1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 08:52:55 +08:00

Merge pull request #24291 from peppy/editor-cyclic-selection

Add support for cyclic selection in beatmap editor
This commit is contained in:
Bartłomiej Dach 2023-07-20 21:31:08 +02:00 committed by GitHub
commit 166efd1aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 21 deletions

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -82,7 +80,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestNudgeSelection() public void TestNudgeSelection()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -104,7 +102,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestRotateHotkeys() public void TestRotateHotkeys()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -136,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestGlobalFlipHotkeys() public void TestGlobalFlipHotkeys()
{ {
HitCircle addedObject = null; HitCircle addedObject = null!;
AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 })); AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 }));
@ -286,11 +284,104 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
} }
[Test]
public void TestCyclicSelection()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
}
[Test]
public void TestCyclicSelectionOutwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek near second", () => EditorClock.Seek(320));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
}
[Test]
public void TestCyclicSelectionBackwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek to third", () => EditorClock.Seek(600));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
}
[Test]
public void TestDoubleClickToSeek()
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { hitCircle }));
moveMouseToObject(() => hitCircle);
AddRepeatStep("double click", () => InputManager.Click(MouseButton.Left), 2);
AddUntilStep("seeked to circle", () => EditorClock.CurrentTime, () => Is.EqualTo(600));
}
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag)
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -389,7 +480,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestQuickDeleteRemovesSliderControlPoint() public void TestQuickDeleteRemovesSliderControlPoint()
{ {
Slider slider = null; Slider slider = null!;
PathControlPoint[] points = PathControlPoint[] points =
{ {

View File

@ -25,8 +25,6 @@ namespace osu.Game.Overlays.SkinEditor
[Resolved] [Resolved]
private SkinEditor editor { get; set; } = null!; private SkinEditor editor { get; set; } = null!;
protected override bool AllowCyclicSelection => true;
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer) public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
{ {
this.targetContainer = targetContainer; this.targetContainer = targetContainer;

View File

@ -46,15 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected readonly BindableList<T> SelectedItems = new BindableList<T>(); protected readonly BindableList<T> SelectedItems = new BindableList<T>();
/// <summary>
/// Whether to allow cyclic selection on clicking multiple times.
/// </summary>
/// <remarks>
/// Disabled by default as it does not work well with editors that support double-clicking or other advanced interactions.
/// Can probably be made to work with more thought.
/// </remarks>
protected virtual bool AllowCyclicSelection => false;
protected BlueprintContainer() protected BlueprintContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -167,6 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint)
return false; return false;
doubleClickHandled = true;
return true; return true;
} }
@ -177,6 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
endClickSelection(e); endClickSelection(e);
clickSelectionHandled = false; clickSelectionHandled = false;
doubleClickHandled = false;
isDraggingBlueprint = false; isDraggingBlueprint = false;
wasDragStarted = false; wasDragStarted = false;
}); });
@ -376,6 +369,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private bool clickSelectionHandled; private bool clickSelectionHandled;
/// <summary>
/// Whether a blueprint was double-clicked since last mouse down.
/// </summary>
private bool doubleClickHandled;
/// <summary> /// <summary>
/// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case. /// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case.
/// </summary> /// </summary>
@ -427,8 +425,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <returns>Whether a click selection was active.</returns> /// <returns>Whether a click selection was active.</returns>
private bool endClickSelection(MouseButtonEvent e) private bool endClickSelection(MouseButtonEvent e)
{ {
// If already handled a selection or drag, we don't want to perform a mouse up / click action. // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action.
if (clickSelectionHandled || isDraggingBlueprint) return true; if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint) return true;
if (e.Button != MouseButton.Left) return false; if (e.Button != MouseButton.Left) return false;
@ -444,7 +442,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false; return false;
} }
if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1 && AllowCyclicSelection) if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1)
{ {
// If a click occurred and was handled by the currently selected blueprint but didn't result in a drag, // If a click occurred and was handled by the currently selected blueprint but didn't result in a drag,
// cycle between other blueprints which are also under the cursor. // cycle between other blueprints which are also under the cursor.