mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 21:52:55 +08:00
Merge branch 'master' into inherit-addition
This commit is contained in:
commit
c524c23db6
@ -0,0 +1,36 @@
|
|||||||
|
// 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.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneCatchReplayHandling : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestReplayDetach()
|
||||||
|
{
|
||||||
|
DrawableCatchRuleset drawableRuleset = null!;
|
||||||
|
float catcherPosition = 0;
|
||||||
|
|
||||||
|
AddStep("create drawable ruleset", () => Child = drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), new CatchBeatmap(), []));
|
||||||
|
AddStep("attach replay", () => drawableRuleset.SetReplayScore(new Score()));
|
||||||
|
AddStep("store catcher position", () => catcherPosition = drawableRuleset.ChildrenOfType<Catcher>().Single().X);
|
||||||
|
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||||
|
AddAssert("catcher didn't move", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.EqualTo(catcherPosition));
|
||||||
|
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||||
|
|
||||||
|
AddStep("detach replay", () => drawableRuleset.SetReplayScore(null));
|
||||||
|
AddStep("hold down left", () => InputManager.PressKey(Key.Left));
|
||||||
|
AddUntilStep("catcher moved", () => drawableRuleset.ChildrenOfType<Catcher>().Single().X, () => Is.Not.EqualTo(catcherPosition));
|
||||||
|
AddStep("release left", () => InputManager.ReleaseKey(Key.Left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -192,12 +192,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
if (press)
|
if (press)
|
||||||
{
|
{
|
||||||
inputManager?.KeyBindingContainer?.TriggerPressed(Action.Value);
|
inputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
|
||||||
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
|
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
inputManager?.KeyBindingContainer?.TriggerReleased(Action.Value);
|
inputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
|
||||||
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
|
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
{
|
{
|
||||||
@ -177,6 +178,79 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
addAssertPointPositionChanged(points, i);
|
addAssertPointPositionChanged(points, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangingControlPointTypeViaTab()
|
||||||
|
{
|
||||||
|
createVisualiser(true);
|
||||||
|
|
||||||
|
addControlPointStep(new Vector2(200), PathType.LINEAR);
|
||||||
|
addControlPointStep(new Vector2(300));
|
||||||
|
addControlPointStep(new Vector2(500, 300));
|
||||||
|
addControlPointStep(new Vector2(700, 200));
|
||||||
|
addControlPointStep(new Vector2(500, 100));
|
||||||
|
|
||||||
|
AddStep("select first control point", () => visualiser.Pieces[0].IsSelected.Value = true);
|
||||||
|
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||||
|
assertControlPointPathType(0, PathType.BEZIER);
|
||||||
|
|
||||||
|
AddStep("press shift-tab", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.Tab);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
assertControlPointPathType(0, PathType.LINEAR);
|
||||||
|
|
||||||
|
AddStep("press shift-tab", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.Tab);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
assertControlPointPathType(0, PathType.BSpline(4));
|
||||||
|
|
||||||
|
AddStep("press shift-tab", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.Tab);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
assertControlPointPathType(0, PathType.PERFECT_CURVE);
|
||||||
|
assertControlPointPathType(2, PathType.BSpline(4));
|
||||||
|
|
||||||
|
AddStep("select third last control point", () =>
|
||||||
|
{
|
||||||
|
visualiser.Pieces[0].IsSelected.Value = false;
|
||||||
|
visualiser.Pieces[2].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("press shift-tab", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LShift);
|
||||||
|
InputManager.Key(Key.Tab);
|
||||||
|
InputManager.ReleaseKey(Key.LShift);
|
||||||
|
});
|
||||||
|
assertControlPointPathType(2, PathType.PERFECT_CURVE);
|
||||||
|
|
||||||
|
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||||
|
assertControlPointPathType(0, PathType.BEZIER);
|
||||||
|
assertControlPointPathType(2, null);
|
||||||
|
|
||||||
|
AddStep("select first and third control points", () =>
|
||||||
|
{
|
||||||
|
visualiser.Pieces[0].IsSelected.Value = true;
|
||||||
|
visualiser.Pieces[2].IsSelected.Value = true;
|
||||||
|
});
|
||||||
|
AddStep("press alt-1", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.AltLeft);
|
||||||
|
InputManager.Key(Key.Number1);
|
||||||
|
InputManager.ReleaseKey(Key.AltLeft);
|
||||||
|
});
|
||||||
|
assertControlPointPathType(0, PathType.LINEAR);
|
||||||
|
assertControlPointPathType(2, PathType.LINEAR);
|
||||||
|
}
|
||||||
|
|
||||||
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
private void addAssertPointPositionChanged(Vector2[] points, int index)
|
||||||
{
|
{
|
||||||
AddAssert($"Point at {points.ElementAt(index)} changed",
|
AddAssert($"Point at {points.ElementAt(index)} changed",
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -57,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertLength(200);
|
assertLength(200);
|
||||||
assertControlPointCount(2);
|
assertControlPointCount(2);
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -71,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(2);
|
assertControlPointCount(2);
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -89,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointPosition(1, new Vector2(100, 0));
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -111,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertControlPointCount(4);
|
assertControlPointCount(4);
|
||||||
assertControlPointPosition(1, new Vector2(100, 0));
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
assertControlPointPosition(2, new Vector2(100, 100));
|
assertControlPointPosition(2, new Vector2(100, 100));
|
||||||
assertControlPointType(0, PathType.BEZIER);
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -130,8 +133,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointPosition(1, new Vector2(100, 0));
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
assertControlPointType(1, PathType.LINEAR);
|
assertFinalControlPointType(1, PathType.LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -149,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(2);
|
assertControlPointCount(2);
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
assertLength(100);
|
assertLength(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -195,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(4);
|
assertControlPointCount(4);
|
||||||
assertControlPointType(0, PathType.BEZIER);
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -215,8 +218,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointPosition(1, new Vector2(100, 0));
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
assertControlPointPosition(2, new Vector2(100));
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
assertControlPointType(1, PathType.LINEAR);
|
assertFinalControlPointType(1, PathType.LINEAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -239,8 +242,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertControlPointCount(4);
|
assertControlPointCount(4);
|
||||||
assertControlPointPosition(1, new Vector2(100, 0));
|
assertControlPointPosition(1, new Vector2(100, 0));
|
||||||
assertControlPointPosition(2, new Vector2(100));
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
assertControlPointType(0, PathType.LINEAR);
|
assertFinalControlPointType(0, PathType.LINEAR);
|
||||||
assertControlPointType(1, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(1, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -268,8 +271,46 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertControlPointPosition(2, new Vector2(100));
|
assertControlPointPosition(2, new Vector2(100));
|
||||||
assertControlPointPosition(3, new Vector2(200, 100));
|
assertControlPointPosition(3, new Vector2(200, 100));
|
||||||
assertControlPointPosition(4, new Vector2(200));
|
assertControlPointPosition(4, new Vector2(200));
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
assertControlPointType(2, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManualPathTypeControlViaKeyboard()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300, 200));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(300));
|
||||||
|
|
||||||
|
assertControlPointTypeDuringPlacement(0, PathType.PERFECT_CURVE);
|
||||||
|
|
||||||
|
AddRepeatStep("press tab", () => InputManager.Key(Key.Tab), 2);
|
||||||
|
assertControlPointTypeDuringPlacement(0, PathType.LINEAR);
|
||||||
|
|
||||||
|
AddStep("press shift-tab", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Key(Key.Tab);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
assertControlPointTypeDuringPlacement(0, PathType.BSpline(4));
|
||||||
|
|
||||||
|
AddStep("start new segment via S", () => InputManager.Key(Key.S));
|
||||||
|
assertControlPointTypeDuringPlacement(2, PathType.LINEAR);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 300));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||||
|
assertFinalControlPointType(2, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -293,7 +334,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
addClickStep(MouseButton.Right);
|
addClickStep(MouseButton.Right);
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
|
|
||||||
assertControlPointType(0, PathType.BEZIER);
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -312,11 +353,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertLength(808, tolerance: 10);
|
assertLength(808, tolerance: 10);
|
||||||
assertControlPointCount(5);
|
assertControlPointCount(5);
|
||||||
assertControlPointType(0, PathType.BSpline(4));
|
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||||
assertControlPointType(1, null);
|
assertFinalControlPointType(1, null);
|
||||||
assertControlPointType(2, null);
|
assertFinalControlPointType(2, null);
|
||||||
assertControlPointType(3, null);
|
assertFinalControlPointType(3, null);
|
||||||
assertControlPointType(4, null);
|
assertFinalControlPointType(4, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -337,10 +378,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertLength(600, tolerance: 10);
|
assertLength(600, tolerance: 10);
|
||||||
assertControlPointCount(4);
|
assertControlPointCount(4);
|
||||||
assertControlPointType(0, PathType.BSpline(4));
|
assertFinalControlPointType(0, PathType.BSpline(4));
|
||||||
assertControlPointType(1, PathType.BSpline(4));
|
assertFinalControlPointType(1, PathType.BSpline(4));
|
||||||
assertControlPointType(2, PathType.BSpline(4));
|
assertFinalControlPointType(2, PathType.BSpline(4));
|
||||||
assertControlPointType(3, null);
|
assertFinalControlPointType(3, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -359,7 +400,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.BEZIER);
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -379,7 +420,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -400,7 +441,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -421,7 +462,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.BEZIER);
|
assertFinalControlPointType(0, PathType.BEZIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -438,7 +479,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(true);
|
||||||
assertControlPointCount(3);
|
assertControlPointCount(3);
|
||||||
assertControlPointType(0, PathType.PERFECT_CURVE);
|
assertFinalControlPointType(0, PathType.PERFECT_CURVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||||
@ -454,7 +495,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected));
|
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider()!.Path.ControlPoints.Count, () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private void assertControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
private void assertControlPointTypeDuringPlacement(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}",
|
||||||
|
() => this.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(index).ControlPoint.Type, () => Is.EqualTo(type));
|
||||||
|
|
||||||
|
private void assertFinalControlPointType(int index, PathType? type) => AddAssert($"control point {index} is {type?.ToString() ?? "inherit"}", () => getSlider()!.Path.ControlPoints[index].Type, () => Is.EqualTo(type));
|
||||||
|
|
||||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
|
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider()!.Path.ControlPoints[index].Position, 1));
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
where T : OsuHitObject, IHasPath
|
where T : OsuHitObject, IHasPath
|
||||||
{
|
{
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield.
|
||||||
|
|
||||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||||
|
|
||||||
@ -196,6 +196,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (allowSelection)
|
if (allowSelection)
|
||||||
d.RequestSelection = selectionRequested;
|
d.RequestSelection = selectionRequested;
|
||||||
|
|
||||||
|
d.ControlPoint.Changed += controlPointChanged;
|
||||||
d.DragStarted = DragStarted;
|
d.DragStarted = DragStarted;
|
||||||
d.DragInProgress = DragInProgress;
|
d.DragInProgress = DragInProgress;
|
||||||
d.DragEnded = DragEnded;
|
d.DragEnded = DragEnded;
|
||||||
@ -209,6 +210,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
foreach (var point in e.OldItems.Cast<PathControlPoint>())
|
||||||
{
|
{
|
||||||
|
point.Changed -= controlPointChanged;
|
||||||
|
|
||||||
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray())
|
||||||
piece.RemoveAndDisposeImmediately();
|
piece.RemoveAndDisposeImmediately();
|
||||||
}
|
}
|
||||||
@ -217,6 +220,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void controlPointChanged() => updateCurveMenuItems();
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (Pieces.Any(piece => piece.IsHovered))
|
if (Pieces.Any(piece => piece.IsHovered))
|
||||||
@ -245,6 +250,86 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once StaticMemberInGenericType
|
||||||
|
private static readonly PathType?[] path_types =
|
||||||
|
[
|
||||||
|
PathType.LINEAR,
|
||||||
|
PathType.BEZIER,
|
||||||
|
PathType.PERFECT_CURVE,
|
||||||
|
PathType.BSpline(4),
|
||||||
|
null,
|
||||||
|
];
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.Tab:
|
||||||
|
{
|
||||||
|
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToArray();
|
||||||
|
if (selectedPieces.Length != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var selectedPiece = selectedPieces.Single();
|
||||||
|
var selectedPoint = selectedPiece.ControlPoint;
|
||||||
|
|
||||||
|
var validTypes = path_types;
|
||||||
|
|
||||||
|
if (selectedPoint == controlPoints[0])
|
||||||
|
validTypes = validTypes.Where(t => t != null).ToArray();
|
||||||
|
|
||||||
|
int currentTypeIndex = Array.IndexOf(validTypes, selectedPoint.Type);
|
||||||
|
|
||||||
|
if (currentTypeIndex < 0 && e.ShiftPressed)
|
||||||
|
currentTypeIndex = 0;
|
||||||
|
|
||||||
|
changeHandler?.BeginChange();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
|
||||||
|
|
||||||
|
updatePathTypeOfSelectedPieces(validTypes[currentTypeIndex]);
|
||||||
|
} while (selectedPoint.Type != validTypes[currentTypeIndex]);
|
||||||
|
|
||||||
|
changeHandler?.EndChange();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Key.Number1:
|
||||||
|
case Key.Number2:
|
||||||
|
case Key.Number3:
|
||||||
|
case Key.Number4:
|
||||||
|
case Key.Number5:
|
||||||
|
{
|
||||||
|
if (!e.AltPressed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var type = path_types[e.Key - Key.Number1];
|
||||||
|
|
||||||
|
if (Pieces[0].IsSelected.Value && type == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
updatePathTypeOfSelectedPieces(type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
foreach (var p in Pieces)
|
||||||
|
p.ControlPoint.Changed -= controlPointChanged;
|
||||||
|
}
|
||||||
|
|
||||||
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
||||||
@ -254,30 +339,38 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to set the given control point piece to the given path type.
|
/// Attempts to set all selected control point pieces to the given path type.
|
||||||
/// If that would fail, try to change the path such that it instead succeeds
|
/// If that fails, try to change the path such that it instead succeeds
|
||||||
/// in a UX-friendly way.
|
/// in a UX-friendly way.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="piece">The control point piece that we want to change the path type of.</param>
|
|
||||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||||
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
private void updatePathTypeOfSelectedPieces(PathType? type)
|
||||||
{
|
{
|
||||||
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
|
changeHandler?.BeginChange();
|
||||||
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
|
|
||||||
|
|
||||||
if (type?.Type == SplineType.PerfectCurve)
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
{
|
{
|
||||||
// Can't always create a circular arc out of 4 or more points,
|
var pointsInSegment = hitObject.Path.PointsInSegment(p.ControlPoint);
|
||||||
// so we split the segment into one 3-point circular arc segment
|
int indexInSegment = pointsInSegment.IndexOf(p.ControlPoint);
|
||||||
// and one segment of the previous type.
|
|
||||||
int thirdPointIndex = indexInSegment + 2;
|
|
||||||
|
|
||||||
if (pointsInSegment.Count > thirdPointIndex + 1)
|
if (type?.Type == SplineType.PerfectCurve)
|
||||||
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
{
|
||||||
|
// Can't always create a circular arc out of 4 or more points,
|
||||||
|
// so we split the segment into one 3-point circular arc segment
|
||||||
|
// and one segment of the previous type.
|
||||||
|
int thirdPointIndex = indexInSegment + 2;
|
||||||
|
|
||||||
|
if (pointsInSegment.Count > thirdPointIndex + 1)
|
||||||
|
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitObject.Path.ExpectedDistance.Value = null;
|
||||||
|
p.ControlPoint.Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
hitObject.Path.ExpectedDistance.Value = null;
|
EnsureValidPathTypes();
|
||||||
piece.ControlPoint.Type = type;
|
|
||||||
|
changeHandler?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
@ -290,6 +383,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private int draggedControlPointIndex;
|
private int draggedControlPointIndex;
|
||||||
private HashSet<PathControlPoint> selectedControlPoints;
|
private HashSet<PathControlPoint> selectedControlPoints;
|
||||||
|
|
||||||
|
private List<MenuItem> curveTypeItems;
|
||||||
|
|
||||||
public void DragStarted(PathControlPoint controlPoint)
|
public void DragStarted(PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
|
dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
|
||||||
@ -386,22 +481,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
var splittablePieces = selectedPieces.Where(isSplittable).ToList();
|
var splittablePieces = selectedPieces.Where(isSplittable).ToList();
|
||||||
int splittableCount = splittablePieces.Count;
|
int splittableCount = splittablePieces.Count;
|
||||||
|
|
||||||
List<MenuItem> curveTypeItems = new List<MenuItem>();
|
curveTypeItems = new List<MenuItem>();
|
||||||
|
|
||||||
if (!selectedPieces.Contains(Pieces[0]))
|
foreach (PathType? type in path_types)
|
||||||
{
|
{
|
||||||
curveTypeItems.Add(createMenuItemForPathType(null));
|
// special inherit case
|
||||||
curveTypeItems.Add(new OsuMenuItemSpacer());
|
if (type == null)
|
||||||
|
{
|
||||||
|
if (selectedPieces.Contains(Pieces[0]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||||
|
}
|
||||||
|
|
||||||
|
curveTypeItems.Add(createMenuItemForPathType(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: hide/disable items which aren't valid for selected points
|
|
||||||
curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR));
|
|
||||||
curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE));
|
|
||||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER));
|
|
||||||
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(4)));
|
|
||||||
|
|
||||||
if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull))
|
if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull))
|
||||||
|
{
|
||||||
|
curveTypeItems.Add(new OsuMenuItemSpacer());
|
||||||
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
|
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
|
||||||
|
}
|
||||||
|
|
||||||
var menuItems = new List<MenuItem>
|
var menuItems = new List<MenuItem>
|
||||||
{
|
{
|
||||||
@ -424,35 +524,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
() => DeleteSelected())
|
() => DeleteSelected())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
updateCurveMenuItems();
|
||||||
|
|
||||||
return menuItems.ToArray();
|
return menuItems.ToArray();
|
||||||
|
|
||||||
|
CurveTypeMenuItem createMenuItemForPathType(PathType? type) => new CurveTypeMenuItem(type, _ => updatePathTypeOfSelectedPieces(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MenuItem createMenuItemForPathType(PathType? type)
|
private void updateCurveMenuItems()
|
||||||
{
|
{
|
||||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
if (curveTypeItems == null)
|
||||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
|
return;
|
||||||
|
|
||||||
var item = new TernaryStateRadioMenuItem(type?.Description ?? "Inherit", MenuItemType.Standard, _ =>
|
foreach (var item in curveTypeItems.OfType<CurveTypeMenuItem>())
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||||
|
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == item.PathType);
|
||||||
|
|
||||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
if (countOfState == totalCount)
|
||||||
updatePathType(p, type);
|
item.State.Value = TernaryState.True;
|
||||||
|
else if (countOfState > 0)
|
||||||
|
item.State.Value = TernaryState.Indeterminate;
|
||||||
|
else
|
||||||
|
item.State.Value = TernaryState.False;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EnsureValidPathTypes();
|
private class CurveTypeMenuItem : TernaryStateRadioMenuItem
|
||||||
|
{
|
||||||
|
public readonly PathType? PathType;
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
public CurveTypeMenuItem(PathType? pathType, Action<TernaryState> action)
|
||||||
});
|
: base(pathType?.Description ?? "Inherit", MenuItemType.Standard, action)
|
||||||
|
{
|
||||||
if (countOfState == totalCount)
|
PathType = pathType;
|
||||||
item.State.Value = TernaryState.True;
|
}
|
||||||
else if (countOfState > 0)
|
|
||||||
item.State.Value = TernaryState.Indeterminate;
|
|
||||||
else
|
|
||||||
item.State.Value = TernaryState.False;
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private PathControlPoint segmentStart;
|
private PathControlPoint segmentStart;
|
||||||
private PathControlPoint cursor;
|
private PathControlPoint cursor;
|
||||||
private int currentSegmentLength;
|
private int currentSegmentLength;
|
||||||
|
private bool usingCustomSegmentType;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
@ -149,21 +150,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
case SliderPlacementState.ControlPoints:
|
case SliderPlacementState.ControlPoints:
|
||||||
if (canPlaceNewControlPoint(out var lastPoint))
|
if (canPlaceNewControlPoint(out var lastPoint))
|
||||||
{
|
placeNewControlPoint();
|
||||||
// Place a new point by detatching the current cursor.
|
|
||||||
updateCursor();
|
|
||||||
cursor = null;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
beginNewSegment(lastPoint);
|
||||||
// Transform the last point into a new segment.
|
|
||||||
Debug.Assert(lastPoint != null);
|
|
||||||
|
|
||||||
segmentStart = lastPoint;
|
|
||||||
segmentStart.Type = PathType.LINEAR;
|
|
||||||
|
|
||||||
currentSegmentLength = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -171,6 +160,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void beginNewSegment(PathControlPoint lastPoint)
|
||||||
|
{
|
||||||
|
// Transform the last point into a new segment.
|
||||||
|
Debug.Assert(lastPoint != null);
|
||||||
|
|
||||||
|
segmentStart = lastPoint;
|
||||||
|
segmentStart.Type = PathType.LINEAR;
|
||||||
|
|
||||||
|
currentSegmentLength = 1;
|
||||||
|
usingCustomSegmentType = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button != MouseButton.Left)
|
if (e.Button != MouseButton.Left)
|
||||||
@ -223,6 +224,72 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly PathType[] path_types =
|
||||||
|
[
|
||||||
|
PathType.LINEAR,
|
||||||
|
PathType.BEZIER,
|
||||||
|
PathType.PERFECT_CURVE,
|
||||||
|
PathType.BSpline(4),
|
||||||
|
];
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (state != SliderPlacementState.ControlPoints)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.S:
|
||||||
|
{
|
||||||
|
if (!canPlaceNewControlPoint(out _))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
placeNewControlPoint();
|
||||||
|
var last = HitObject.Path.ControlPoints.Last(p => p != cursor);
|
||||||
|
beginNewSegment(last);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Key.Number1:
|
||||||
|
case Key.Number2:
|
||||||
|
case Key.Number3:
|
||||||
|
case Key.Number4:
|
||||||
|
{
|
||||||
|
if (!e.AltPressed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
usingCustomSegmentType = true;
|
||||||
|
segmentStart.Type = path_types[e.Key - Key.Number1];
|
||||||
|
controlPointVisualiser.EnsureValidPathTypes();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Key.Tab:
|
||||||
|
{
|
||||||
|
usingCustomSegmentType = true;
|
||||||
|
|
||||||
|
int currentTypeIndex = segmentStart.Type.HasValue ? Array.IndexOf(path_types, segmentStart.Type.Value) : -1;
|
||||||
|
|
||||||
|
if (currentTypeIndex < 0 && e.ShiftPressed)
|
||||||
|
currentTypeIndex = 0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
currentTypeIndex = (path_types.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % path_types.Length;
|
||||||
|
segmentStart.Type = path_types[currentTypeIndex];
|
||||||
|
controlPointVisualiser.EnsureValidPathTypes();
|
||||||
|
} while (segmentStart.Type != path_types[currentTypeIndex]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -246,6 +313,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void updatePathType()
|
private void updatePathType()
|
||||||
{
|
{
|
||||||
|
if (usingCustomSegmentType)
|
||||||
|
{
|
||||||
|
controlPointVisualiser.EnsureValidPathTypes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (state == SliderPlacementState.Drawing)
|
if (state == SliderPlacementState.Drawing)
|
||||||
{
|
{
|
||||||
segmentStart.Type = PathType.BSpline(4);
|
segmentStart.Type = PathType.BSpline(4);
|
||||||
@ -316,6 +389,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
return lastPiece.IsHovered != true;
|
return lastPiece.IsHovered != true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void placeNewControlPoint()
|
||||||
|
{
|
||||||
|
// Place a new point by detatching the current cursor.
|
||||||
|
updateCursor();
|
||||||
|
cursor = null;
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
if (state == SliderPlacementState.Drawing)
|
if (state == SliderPlacementState.Drawing)
|
||||||
|
@ -15,6 +15,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
public SliderCompositionTool()
|
public SliderCompositionTool()
|
||||||
: base(nameof(Slider))
|
: base(nameof(Slider))
|
||||||
{
|
{
|
||||||
|
TooltipText = """
|
||||||
|
Left click for new point.
|
||||||
|
Left click twice or S key for new segment.
|
||||||
|
Tab, Shift-Tab, or Alt-1~4 to change current segment type.
|
||||||
|
Right click to finish.
|
||||||
|
Click and drag for drawing mode.
|
||||||
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
235
osu.Game.Tests/Editing/Checks/CheckTitleMarkersTest.cs
Normal file
235
osu.Game.Tests/Editing/Checks/CheckTitleMarkersTest.cs
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// 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.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckTitleMarkersTest
|
||||||
|
{
|
||||||
|
private CheckTitleMarkers check = null!;
|
||||||
|
|
||||||
|
private IBeatmap beatmap = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckTitleMarkers();
|
||||||
|
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = "Egao no Kanata",
|
||||||
|
TitleUnicode = "エガオノカナタ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoTitleMarkers()
|
||||||
|
{
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTvSizeMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (TV Size)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (TV Size)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedTvSizeMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (tv size)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (tv size)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGameVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Game Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Game Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedGameVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (game ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (game ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Short Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Short Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedShortVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (short ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (short ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Cut Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Cut Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (cut ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (cut ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpedUpVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Sped Up Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Sped Up Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedSpedUpVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (sped up ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (sped up ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNightcoreMixMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Nightcore Mix)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Nightcore Mix)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedNightcoreMixMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (nightcore mix)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (nightcore mix)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpedUpCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Sped Up & Cut Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Sped Up & Cut Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedSpedUpCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (sped up & cut ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (sped up & cut ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNightcoreCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (Nightcore & Cut Ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (Nightcore & Cut Ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMalformedNightcoreCutVerMarker()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.Metadata.Title += " (nightcore & cut ver.)";
|
||||||
|
beatmap.BeatmapInfo.Metadata.TitleUnicode += " (nightcore & cut ver.)";
|
||||||
|
|
||||||
|
var issues = check.Run(getContext(beatmap)).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(2));
|
||||||
|
Assert.That(issues.Any(issue => issue.Template is CheckTitleMarkers.IssueTemplateIncorrectMarker));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,9 @@ using osu.Framework.Testing;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Taiko;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
@ -169,6 +171,24 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSwitchToDifficultyOfAnotherRuleset()
|
||||||
|
{
|
||||||
|
BeatmapInfo targetDifficulty = null;
|
||||||
|
|
||||||
|
AddAssert("ruleset is catch", () => Ruleset.Value.CreateInstance() is CatchRuleset);
|
||||||
|
|
||||||
|
AddStep("set taiko difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1));
|
||||||
|
switchToDifficulty(() => targetDifficulty);
|
||||||
|
confirmEditingBeatmap(() => targetDifficulty);
|
||||||
|
|
||||||
|
AddAssert("ruleset switched to taiko", () => Ruleset.Value.CreateInstance() is TaikoRuleset);
|
||||||
|
|
||||||
|
AddStep("exit editor forcefully", () => Stack.Exit());
|
||||||
|
// ensure editor loader didn't resume.
|
||||||
|
AddAssert("stack empty", () => Stack.CurrentScreen == null);
|
||||||
|
}
|
||||||
|
|
||||||
private void switchToDifficulty(Func<BeatmapInfo> difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
|
private void switchToDifficulty(Func<BeatmapInfo> difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke()));
|
||||||
|
|
||||||
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
|
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
@ -224,6 +225,116 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
|
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutoplayToggle()
|
||||||
|
{
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
AddUntilStep("no replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Null);
|
||||||
|
AddStep("press Tab", () => InputManager.Key(Key.Tab));
|
||||||
|
AddUntilStep("replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Not.Null);
|
||||||
|
AddStep("press Tab", () => InputManager.Key(Key.Tab));
|
||||||
|
AddUntilStep("no replay active", () => editorPlayer.ChildrenOfType<DrawableRuleset>().Single().ReplayScore, () => Is.Null);
|
||||||
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuickPause()
|
||||||
|
{
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
AddUntilStep("clock running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.False);
|
||||||
|
AddStep("press Ctrl-P", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddUntilStep("clock not running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.True);
|
||||||
|
AddStep("press Ctrl-P", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Key(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
AddUntilStep("clock running", () => editorPlayer.ChildrenOfType<GameplayClockContainer>().Single().IsPaused.Value, () => Is.False);
|
||||||
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuickExitAtInitialPosition()
|
||||||
|
{
|
||||||
|
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
|
||||||
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
|
||||||
|
AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning);
|
||||||
|
// when the gameplay test is entered, the clock is expected to continue from where it was in the main editor...
|
||||||
|
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 5);
|
||||||
|
|
||||||
|
AddStep("exit player", () => InputManager.PressKey(Key.F1));
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestQuickExitAtCurrentPosition()
|
||||||
|
{
|
||||||
|
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
|
||||||
|
GameplayClockContainer gameplayClockContainer = null;
|
||||||
|
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
|
||||||
|
AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning);
|
||||||
|
// when the gameplay test is entered, the clock is expected to continue from where it was in the main editor...
|
||||||
|
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
|
||||||
|
|
||||||
|
AddWaitStep("wait some", 5);
|
||||||
|
|
||||||
|
AddStep("exit player", () => InputManager.PressKey(Key.F2));
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddAssert("time moved forward", () => EditorClock.CurrentTime, () => Is.GreaterThan(60_000));
|
||||||
|
}
|
||||||
|
|
||||||
public override void TearDownSteps()
|
public override void TearDownSteps()
|
||||||
{
|
{
|
||||||
base.TearDownSteps();
|
base.TearDownSteps();
|
||||||
|
@ -34,6 +34,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => globalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => globalKeyBindings
|
||||||
.Concat(editorKeyBindings)
|
.Concat(editorKeyBindings)
|
||||||
|
.Concat(editorTestPlayKeyBindings)
|
||||||
.Concat(inGameKeyBindings)
|
.Concat(inGameKeyBindings)
|
||||||
.Concat(replayKeyBindings)
|
.Concat(replayKeyBindings)
|
||||||
.Concat(songSelectKeyBindings)
|
.Concat(songSelectKeyBindings)
|
||||||
@ -68,6 +69,9 @@ namespace osu.Game.Input.Bindings
|
|||||||
case GlobalActionCategory.Overlays:
|
case GlobalActionCategory.Overlays:
|
||||||
return overlayKeyBindings;
|
return overlayKeyBindings;
|
||||||
|
|
||||||
|
case GlobalActionCategory.EditorTestPlay:
|
||||||
|
return editorTestPlayKeyBindings;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(category), category, $"Unexpected {nameof(GlobalActionCategory)}");
|
throw new ArgumentOutOfRangeException(nameof(category), category, $"Unexpected {nameof(GlobalActionCategory)}");
|
||||||
}
|
}
|
||||||
@ -100,7 +104,6 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.S }, GlobalAction.ToggleSkinEditor),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.ToggleProfile),
|
|
||||||
|
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
|
||||||
|
|
||||||
@ -118,6 +121,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.ToggleBeatmapListing),
|
new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.ToggleBeatmapListing),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.ToggleProfile),
|
||||||
};
|
};
|
||||||
|
|
||||||
private static IEnumerable<KeyBinding> editorKeyBindings => new[]
|
private static IEnumerable<KeyBinding> editorKeyBindings => new[]
|
||||||
@ -145,6 +149,14 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static IEnumerable<KeyBinding> editorTestPlayKeyBindings => new[]
|
||||||
|
{
|
||||||
|
new KeyBinding(new[] { InputKey.Tab }, GlobalAction.EditorTestPlayToggleAutoplay),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.P }, GlobalAction.EditorTestPlayToggleQuickPause),
|
||||||
|
new KeyBinding(new[] { InputKey.F1 }, GlobalAction.EditorTestPlayQuickExitToInitialTime),
|
||||||
|
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorTestPlayQuickExitToCurrentTime),
|
||||||
|
};
|
||||||
|
|
||||||
private static IEnumerable<KeyBinding> inGameKeyBindings => new[]
|
private static IEnumerable<KeyBinding> inGameKeyBindings => new[]
|
||||||
{
|
{
|
||||||
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
|
new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene),
|
||||||
@ -432,6 +444,18 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))]
|
||||||
EditorToggleScaleControl,
|
EditorToggleScaleControl,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayToggleAutoplay))]
|
||||||
|
EditorTestPlayToggleAutoplay,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayToggleQuickPause))]
|
||||||
|
EditorTestPlayToggleQuickPause,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToInitialTime))]
|
||||||
|
EditorTestPlayQuickExitToInitialTime,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestPlayQuickExitToCurrentTime))]
|
||||||
|
EditorTestPlayQuickExitToCurrentTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum GlobalActionCategory
|
public enum GlobalActionCategory
|
||||||
@ -442,6 +466,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
Replay,
|
Replay,
|
||||||
SongSelect,
|
SongSelect,
|
||||||
AudioControl,
|
AudioControl,
|
||||||
Overlays
|
Overlays,
|
||||||
|
EditorTestPlay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,6 +374,26 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control");
|
public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Toggle autoplay"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTestPlayToggleAutoplay => new TranslatableString(getKey(@"editor_test_play_toggle_autoplay"), @"Toggle autoplay");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Toggle quick pause"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTestPlayToggleQuickPause => new TranslatableString(getKey(@"editor_test_play_toggle_quick_pause"), @"Toggle quick pause");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Quick exit to initial time"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTestPlayQuickExitToInitialTime => new TranslatableString(getKey(@"editor_test_play_quick_exit_to_initial_time"), @"Quick exit to initial time");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Quick exit to current time"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTestPlayQuickExitToCurrentTime => new TranslatableString(getKey(@"editor_test_play_quick_exit_to_current_time"), @"Quick exit to current time");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Increase mod speed"
|
/// "Increase mod speed"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -49,6 +49,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
|
public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Editor: Test play"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTestPlaySection => new TranslatableString(getKey(@"editor_test_play_section"), @"Editor: Test play");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Reset all bindings in section"
|
/// "Reset all bindings in section"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.InGameSection, GlobalActionCategory.InGame),
|
new GlobalKeyBindingsSubsection(InputSettingsStrings.InGameSection, GlobalActionCategory.InGame),
|
||||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.ReplaySection, GlobalActionCategory.Replay),
|
new GlobalKeyBindingsSubsection(InputSettingsStrings.ReplaySection, GlobalActionCategory.Replay),
|
||||||
new GlobalKeyBindingsSubsection(InputSettingsStrings.EditorSection, GlobalActionCategory.Editor),
|
new GlobalKeyBindingsSubsection(InputSettingsStrings.EditorSection, GlobalActionCategory.Editor),
|
||||||
|
new GlobalKeyBindingsSubsection(InputSettingsStrings.EditorTestPlaySection, GlobalActionCategory.EditorTestPlay),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
// Events
|
// Events
|
||||||
new CheckBreaks(),
|
new CheckBreaks(),
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
new CheckTitleMarkers(),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
71
osu.Game/Rulesets/Edit/Checks/CheckTitleMarkers.cs
Normal file
71
osu.Game/Rulesets/Edit/Checks/CheckTitleMarkers.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckTitleMarkers : ICheck
|
||||||
|
{
|
||||||
|
public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Metadata, "Checks for incorrect formats of (TV Size) / (Game Ver.) / (Short Ver.) / (Cut Ver.) / (Sped Up Ver.) / etc in title.");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateIncorrectMarker(this),
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly IEnumerable<MarkerCheck> markerChecks =
|
||||||
|
[
|
||||||
|
new MarkerCheck(@"(TV Size)", @"(?i)(tv (size|ver))"),
|
||||||
|
new MarkerCheck(@"(Game Ver.)", @"(?i)(game (size|ver))"),
|
||||||
|
new MarkerCheck(@"(Short Ver.)", @"(?i)(short (size|ver))"),
|
||||||
|
new MarkerCheck(@"(Cut Ver.)", @"(?i)(?<!& )(cut (size|ver))"),
|
||||||
|
new MarkerCheck(@"(Sped Up Ver.)", @"(?i)(?<!& )(sped|speed) ?up ver"),
|
||||||
|
new MarkerCheck(@"(Nightcore Mix)", @"(?i)(?<!& )(nightcore|night core) (ver|mix)"),
|
||||||
|
new MarkerCheck(@"(Sped Up & Cut Ver.)", @"(?i)(sped|speed) ?up (ver)? ?& cut (size|ver)"),
|
||||||
|
new MarkerCheck(@"(Nightcore & Cut Ver.)", @"(?i)(nightcore|night core) (ver|mix)? ?& cut (size|ver)"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(BeatmapVerifierContext context)
|
||||||
|
{
|
||||||
|
string romanisedTitle = context.Beatmap.Metadata.Title;
|
||||||
|
string unicodeTitle = context.Beatmap.Metadata.TitleUnicode;
|
||||||
|
|
||||||
|
foreach (var check in markerChecks)
|
||||||
|
{
|
||||||
|
bool hasRomanisedTitle = unicodeTitle != romanisedTitle;
|
||||||
|
|
||||||
|
if (check.AnyRegex.IsMatch(unicodeTitle) && !unicodeTitle.Contains(check.CorrectMarkerFormat, StringComparison.Ordinal))
|
||||||
|
yield return new IssueTemplateIncorrectMarker(this).Create("Title", check.CorrectMarkerFormat);
|
||||||
|
|
||||||
|
if (hasRomanisedTitle && check.AnyRegex.IsMatch(romanisedTitle) && !romanisedTitle.Contains(check.CorrectMarkerFormat, StringComparison.Ordinal))
|
||||||
|
yield return new IssueTemplateIncorrectMarker(this).Create("Romanised title", check.CorrectMarkerFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MarkerCheck
|
||||||
|
{
|
||||||
|
public readonly string CorrectMarkerFormat;
|
||||||
|
public readonly Regex AnyRegex;
|
||||||
|
|
||||||
|
public MarkerCheck(string exact, string anyRegex)
|
||||||
|
{
|
||||||
|
CorrectMarkerFormat = exact;
|
||||||
|
AnyRegex = new Regex(anyRegex, RegexOptions.Compiled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateIncorrectMarker : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateIncorrectMarker(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "{0} field has an incorrect format of marker {1}")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(string titleField, string correctMarkerFormat) => new Issue(this, titleField, correctMarkerFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -215,14 +215,14 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
toolboxCollection.Items = CompositionTools
|
toolboxCollection.Items = CompositionTools
|
||||||
.Prepend(new SelectTool())
|
.Prepend(new SelectTool())
|
||||||
.Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon))
|
.Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t)))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var item in toolboxCollection.Items)
|
foreach (var item in toolboxCollection.Items)
|
||||||
{
|
{
|
||||||
item.Selected.DisabledChanged += isDisabled =>
|
item.Selected.DisabledChanged += isDisabled =>
|
||||||
{
|
{
|
||||||
item.TooltipText = isDisabled ? "Add at least one timing point first!" : string.Empty;
|
item.TooltipText = isDisabled ? "Add at least one timing point first!" : ((HitObjectCompositionToolButton)item).TooltipText;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
Normal file
22
osu.Game/Rulesets/Edit/HitObjectCompositionToolButton.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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.Game.Rulesets.Edit.Tools;
|
||||||
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
public class HitObjectCompositionToolButton : RadioButton
|
||||||
|
{
|
||||||
|
public HitObjectCompositionTool Tool { get; }
|
||||||
|
|
||||||
|
public HitObjectCompositionToolButton(HitObjectCompositionTool tool, Action? action)
|
||||||
|
: base(tool.Name, action, tool.CreateIcon)
|
||||||
|
{
|
||||||
|
Tool = tool;
|
||||||
|
|
||||||
|
TooltipText = tool.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Tools
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
{
|
{
|
||||||
@ -11,14 +10,16 @@ namespace osu.Game.Rulesets.Edit.Tools
|
|||||||
{
|
{
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
|
||||||
|
public LocalisableString TooltipText { get; init; }
|
||||||
|
|
||||||
protected HitObjectCompositionTool(string name)
|
protected HitObjectCompositionTool(string name)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract PlacementBlueprint CreatePlacementBlueprint();
|
public abstract PlacementBlueprint? CreatePlacementBlueprint();
|
||||||
|
|
||||||
public virtual Drawable CreateIcon() => null;
|
public virtual Drawable? CreateIcon() => null;
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
}
|
}
|
||||||
|
@ -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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -12,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Input.StateChanges.Events;
|
using osu.Framework.Input.StateChanges.Events;
|
||||||
using osu.Framework.Input.States;
|
using osu.Framework.Input.States;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -21,6 +20,7 @@ using osu.Game.Input.Handlers;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||||
|
using osuTK;
|
||||||
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
using static osu.Game.Input.Handlers.ReplayInputHandler;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
@ -32,12 +32,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
public readonly KeyBindingContainer<T> KeyBindingContainer;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private ScoreProcessor scoreProcessor { get; set; }
|
private ScoreProcessor? scoreProcessor { get; set; }
|
||||||
|
|
||||||
private ReplayRecorder recorder;
|
private ReplayRecorder? recorder;
|
||||||
|
|
||||||
public ReplayRecorder Recorder
|
public ReplayRecorder? Recorder
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@ -103,14 +103,23 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region IHasReplayHandler
|
#region IHasReplayHandler
|
||||||
|
|
||||||
private ReplayInputHandler replayInputHandler;
|
private ReplayInputHandler? replayInputHandler;
|
||||||
|
|
||||||
public ReplayInputHandler ReplayInputHandler
|
public ReplayInputHandler? ReplayInputHandler
|
||||||
{
|
{
|
||||||
get => replayInputHandler;
|
get => replayInputHandler;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
|
if (replayInputHandler == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (replayInputHandler != null)
|
||||||
|
RemoveHandler(replayInputHandler);
|
||||||
|
|
||||||
|
// ensures that all replay keys are released, that the last replay state is correctly cleared,
|
||||||
|
// and that all user-pressed keys are released, so that the replay handler may trigger them itself
|
||||||
|
// setting `UseParentInput` will only sync releases (https://github.com/ppy/osu-framework/blob/17d65f476d51cc5f2aaea818534f8fbac47e5fe6/osu.Framework/Input/PassThroughInputManager.cs#L179-L182)
|
||||||
|
new ReplayStateReset().Apply(CurrentState, this);
|
||||||
|
|
||||||
replayInputHandler = value;
|
replayInputHandler = value;
|
||||||
UseParentInput = replayInputHandler == null;
|
UseParentInput = replayInputHandler == null;
|
||||||
@ -124,8 +133,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Setting application (disables etc.)
|
#region Setting application (disables etc.)
|
||||||
|
|
||||||
private Bindable<bool> mouseDisabled;
|
private Bindable<bool> mouseDisabled = null!;
|
||||||
private Bindable<bool> tapsDisabled;
|
private Bindable<bool> tapsDisabled = null!;
|
||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
protected override bool Handle(UIEvent e)
|
||||||
{
|
{
|
||||||
@ -222,14 +231,34 @@ namespace osu.Game.Rulesets.UI
|
|||||||
RealmKeyBindingStore.ClearDuplicateBindings(KeyBindings);
|
RealmKeyBindingStore.ClearDuplicateBindings(KeyBindings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ReplayStateReset : IInput
|
||||||
|
{
|
||||||
|
public void Apply(InputState state, IInputStateChangeHandler handler)
|
||||||
|
{
|
||||||
|
if (!(state is RulesetInputManagerInputState<T> inputState))
|
||||||
|
throw new InvalidOperationException($"{nameof(ReplayState<T>)} should only be applied to a {nameof(RulesetInputManagerInputState<T>)}");
|
||||||
|
|
||||||
|
new MouseButtonInput([], state.Mouse.Buttons).Apply(state, handler);
|
||||||
|
new KeyboardKeyInput([], state.Keyboard.Keys).Apply(state, handler);
|
||||||
|
new TouchInput(Enum.GetValues<TouchSource>().Select(s => new Touch(s, Vector2.Zero)), false).Apply(state, handler);
|
||||||
|
new JoystickButtonInput([], state.Joystick.Buttons).Apply(state, handler);
|
||||||
|
new MidiKeyInput(new MidiState(), state.Midi).Apply(state, handler);
|
||||||
|
new TabletPenButtonInput([], state.Tablet.PenButtons).Apply(state, handler);
|
||||||
|
new TabletAuxiliaryButtonInput([], state.Tablet.AuxiliaryButtons).Apply(state, handler);
|
||||||
|
|
||||||
|
handler.HandleInputStateChange(new ReplayStateChangeEvent<T>(state, this, inputState.LastReplayState?.PressedActions.ToArray() ?? [], []));
|
||||||
|
inputState.LastReplayState = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RulesetInputManagerInputState<T> : InputState
|
public class RulesetInputManagerInputState<T> : InputState
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
public ReplayState<T> LastReplayState;
|
public ReplayState<T>? LastReplayState;
|
||||||
|
|
||||||
public RulesetInputManagerInputState(InputState state = null)
|
public RulesetInputManagerInputState(InputState state)
|
||||||
: base(state)
|
: base(state)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,11 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -25,14 +27,16 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
public partial class PlaybackControl : BottomBarContainer
|
public partial class PlaybackControl : BottomBarContainer
|
||||||
{
|
{
|
||||||
private IconButton playButton = null!;
|
private IconButton playButton = null!;
|
||||||
|
private PlaybackSpeedControl playbackSpeedControl = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock editorClock { get; set; } = null!;
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
private readonly BindableNumber<double> freqAdjust = new BindableDouble(1);
|
private readonly Bindable<EditorScreenMode> currentScreenMode = new Bindable<EditorScreenMode>();
|
||||||
|
private readonly BindableNumber<double> tempoAdjustment = new BindableDouble(1);
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider, Editor? editor)
|
||||||
{
|
{
|
||||||
Background.Colour = colourProvider.Background4;
|
Background.Colour = colourProvider.Background4;
|
||||||
|
|
||||||
@ -47,31 +51,61 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
Icon = FontAwesome.Regular.PlayCircle,
|
Icon = FontAwesome.Regular.PlayCircle,
|
||||||
Action = togglePause,
|
Action = togglePause,
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
playbackSpeedControl = new PlaybackSpeedControl
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomLeft,
|
AutoSizeAxes = Axes.Y,
|
||||||
Text = EditorStrings.PlaybackSpeed,
|
RelativeSizeAxes = Axes.X,
|
||||||
RelativePositionAxes = Axes.Y,
|
Padding = new MarginPadding { Left = 45, },
|
||||||
Y = 0.5f,
|
Anchor = Anchor.CentreRight,
|
||||||
Padding = new MarginPadding { Left = 45 }
|
Origin = Anchor.CentreRight,
|
||||||
},
|
Direction = FillDirection.Vertical,
|
||||||
new Container
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomLeft,
|
new OsuSpriteText
|
||||||
Origin = Anchor.BottomLeft,
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
Text = EditorStrings.PlaybackSpeed,
|
||||||
Height = 0.5f,
|
},
|
||||||
Padding = new MarginPadding { Left = 45 },
|
new PlaybackTabControl
|
||||||
Child = new PlaybackTabControl { Current = freqAdjust },
|
{
|
||||||
|
Current = tempoAdjustment,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 16,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true);
|
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjustment), true);
|
||||||
|
|
||||||
|
if (editor != null)
|
||||||
|
currentScreenMode.BindTo(editor.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
currentScreenMode.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
if (currentScreenMode.Value == EditorScreenMode.Timing)
|
||||||
|
{
|
||||||
|
tempoAdjustment.Value = 1;
|
||||||
|
tempoAdjustment.Disabled = true;
|
||||||
|
playbackSpeedControl.FadeTo(0.5f, 400, Easing.OutQuint);
|
||||||
|
playbackSpeedControl.TooltipText = "Speed adjustment is unavailable in timing mode. Timing at slower speeds is inaccurate due to resampling artifacts.";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tempoAdjustment.Disabled = false;
|
||||||
|
playbackSpeedControl.FadeTo(1, 400, Easing.OutQuint);
|
||||||
|
playbackSpeedControl.TooltipText = default;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, tempoAdjustment);
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
@ -109,6 +143,11 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
playButton.Icon = editorClock.IsRunning ? pause_icon : play_icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private partial class PlaybackSpeedControl : FillFlowContainer, IHasTooltip
|
||||||
|
{
|
||||||
|
public LocalisableString TooltipText { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
private partial class PlaybackTabControl : OsuTabControl<double>
|
||||||
{
|
{
|
||||||
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
|
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
|
||||||
@ -174,7 +213,7 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
updateState();
|
updateState();
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e) => updateState();
|
protected override void OnHoverLost(HoverLostEvent e) => updateState();
|
||||||
|
@ -24,11 +24,11 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Func<Drawable>? CreateIcon;
|
public readonly Func<Drawable?>? CreateIcon;
|
||||||
|
|
||||||
private readonly Action? action;
|
private readonly Action? action;
|
||||||
|
|
||||||
public RadioButton(string label, Action? action, Func<Drawable>? createIcon = null)
|
public RadioButton(string label, Action? action, Func<Drawable?>? createIcon = null)
|
||||||
{
|
{
|
||||||
Label = label;
|
Label = label;
|
||||||
CreateIcon = createIcon;
|
CreateIcon = createIcon;
|
||||||
|
@ -121,7 +121,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
scheduledDifficultySwitch = Schedule(() =>
|
scheduledDifficultySwitch = Schedule(() =>
|
||||||
{
|
{
|
||||||
Beatmap.Value = nextBeatmap.Invoke();
|
var workingBeatmap = nextBeatmap.Invoke();
|
||||||
|
|
||||||
|
Ruleset.Value = workingBeatmap.BeatmapInfo.Ruleset;
|
||||||
|
Beatmap.Value = workingBeatmap;
|
||||||
|
|
||||||
state = editorState;
|
state = editorState;
|
||||||
|
|
||||||
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap.
|
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap.
|
||||||
|
@ -4,17 +4,21 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.GameplayTest
|
namespace osu.Game.Screens.Edit.GameplayTest
|
||||||
{
|
{
|
||||||
public partial class EditorPlayer : Player
|
public partial class EditorPlayer : Player, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
private readonly Editor editor;
|
private readonly Editor editor;
|
||||||
private readonly EditorState editorState;
|
private readonly EditorState editorState;
|
||||||
@ -133,6 +137,76 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
|||||||
|
|
||||||
protected override bool CheckModsAllowFailure() => false; // never fail.
|
protected override bool CheckModsAllowFailure() => false; // never fail.
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
if (e.Repeat)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case GlobalAction.EditorTestPlayToggleAutoplay:
|
||||||
|
toggleAutoplay();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorTestPlayToggleQuickPause:
|
||||||
|
toggleQuickPause();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorTestPlayQuickExitToInitialTime:
|
||||||
|
quickExit(false);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorTestPlayQuickExitToCurrentTime:
|
||||||
|
quickExit(true);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleAutoplay()
|
||||||
|
{
|
||||||
|
if (DrawableRuleset.ReplayScore == null)
|
||||||
|
{
|
||||||
|
var autoplay = Ruleset.Value.CreateInstance().GetAutoplayMod();
|
||||||
|
if (autoplay == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var score = autoplay.CreateScoreFromReplayData(GameplayState.Beatmap, [autoplay]);
|
||||||
|
|
||||||
|
// remove past frames to prevent replay frame handler from seeking back to start in an attempt to play back the entirety of the replay.
|
||||||
|
score.Replay.Frames.RemoveAll(f => f.Time <= GameplayClockContainer.CurrentTime);
|
||||||
|
|
||||||
|
DrawableRuleset.SetReplayScore(score);
|
||||||
|
// Without this schedule, the `GlobalCursorDisplay.Update()` machinery will fade the gameplay cursor out, but we still want it to show.
|
||||||
|
Schedule(() => DrawableRuleset.Cursor?.Show());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawableRuleset.SetReplayScore(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleQuickPause()
|
||||||
|
{
|
||||||
|
if (GameplayClockContainer.IsPaused.Value)
|
||||||
|
GameplayClockContainer.Start();
|
||||||
|
else
|
||||||
|
GameplayClockContainer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void quickExit(bool useCurrentTime)
|
||||||
|
{
|
||||||
|
if (useCurrentTime)
|
||||||
|
editorState.Time = GameplayClockContainer.CurrentTime;
|
||||||
|
|
||||||
|
editor.RestoreState(editorState);
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnEntering(ScreenTransitionEvent e)
|
public override void OnEntering(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
base.OnEntering(e);
|
base.OnEntering(e);
|
||||||
|
Loading…
Reference in New Issue
Block a user