mirror of
https://github.com/ppy/osu.git
synced 2025-01-22 23:22:54 +08:00
Attempt to convert slider editing to command pattern
This commit is contained in:
parent
307d52549e
commit
39dc35712c
@ -24,6 +24,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
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.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Commands;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Commands;
|
using osu.Game.Screens.Edit.Commands;
|
||||||
@ -43,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private readonly T hitObject;
|
private readonly T hitObject;
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
|
private SliderPathCommandProxy pathProxy = null;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
public Action<List<PathControlPoint>> RemoveControlPointsRequested;
|
||||||
@ -114,7 +117,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (segment.Count > 3)
|
if (segment.Count > 3)
|
||||||
first.Type = PathType.BEZIER;
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(first) { Type = PathType.BEZIER });
|
||||||
|
|
||||||
if (segment.Count != 3)
|
if (segment.Count != 3)
|
||||||
return;
|
return;
|
||||||
@ -122,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
|
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
|
||||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
first.Type = PathType.BEZIER;
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(first) { Type = PathType.BEZIER });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -147,9 +150,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (toRemove.Count == 0)
|
if (toRemove.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
|
||||||
RemoveControlPointsRequested?.Invoke(toRemove);
|
RemoveControlPointsRequested?.Invoke(toRemove);
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
|
|
||||||
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
foreach (var piece in Pieces)
|
foreach (var piece in Pieces)
|
||||||
@ -166,9 +168,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (controlPointsToSplitAt.Count == 0)
|
if (controlPointsToSplitAt.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
|
||||||
SplitControlPointsRequested?.Invoke(controlPointsToSplitAt);
|
SplitControlPointsRequested?.Invoke(controlPointsToSplitAt);
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
|
|
||||||
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
foreach (var piece in Pieces)
|
foreach (var piece in Pieces)
|
||||||
@ -287,8 +288,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (currentTypeIndex < 0 && e.ShiftPressed)
|
if (currentTypeIndex < 0 && e.ShiftPressed)
|
||||||
currentTypeIndex = 0;
|
currentTypeIndex = 0;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
|
currentTypeIndex = (validTypes.Length + currentTypeIndex + (e.ShiftPressed ? -1 : 1)) % validTypes.Length;
|
||||||
@ -296,7 +295,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
updatePathTypeOfSelectedPieces(validTypes[currentTypeIndex]);
|
updatePathTypeOfSelectedPieces(validTypes[currentTypeIndex]);
|
||||||
} while (selectedPoint.Type != validTypes[currentTypeIndex]);
|
} while (selectedPoint.Type != validTypes[currentTypeIndex]);
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -352,8 +351,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// <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 updatePathTypeOfSelectedPieces(PathType? type)
|
private void updatePathTypeOfSelectedPieces(PathType? type)
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
|
||||||
|
|
||||||
double originalDistance = hitObject.Path.Distance;
|
double originalDistance = hitObject.Path.Distance;
|
||||||
|
|
||||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
@ -383,12 +380,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
else
|
else
|
||||||
hitObject.Path.ExpectedDistance.Value = originalDistance;
|
hitObject.Path.ExpectedDistance.Value = originalDistance;
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private IEditorChangeHandler changeHandler { get; set; }
|
|
||||||
|
|
||||||
#region Drag handling
|
#region Drag handling
|
||||||
|
|
||||||
private Vector2[] dragStartPositions;
|
private Vector2[] dragStartPositions;
|
||||||
@ -407,7 +401,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
Debug.Assert(draggedControlPointIndex >= 0);
|
Debug.Assert(draggedControlPointIndex >= 0);
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
commandHandler?.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
@ -438,7 +432,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
// All other selected control points (if any) will move together with the head point
|
// All other selected control points (if any) will move together with the head point
|
||||||
// (and so they will not move at all, relative to each other).
|
// (and so they will not move at all, relative to each other).
|
||||||
if (!selectedControlPoints.Contains(controlPoint))
|
if (!selectedControlPoints.Contains(controlPoint))
|
||||||
controlPoint.Position -= movementDelta;
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(controlPoint) { Position = controlPoint.Position - movementDelta });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -451,33 +445,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
PathControlPoint controlPoint = controlPoints[i];
|
PathControlPoint controlPoint = controlPoints[i];
|
||||||
if (selectedControlPoints.Contains(controlPoint))
|
if (selectedControlPoints.Contains(controlPoint))
|
||||||
controlPoint.Position = dragStartPositions[i] + movementDelta;
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(controlPoint) { Position = dragStartPositions[i] + movementDelta });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snap the path to the current beat divisor before checking length validity.
|
// Snap the path to the current beat divisor before checking length validity.
|
||||||
hitObject.SnapTo(distanceSnapProvider);
|
hitObject.SnapTo(distanceSnapProvider, commandHandler);
|
||||||
|
|
||||||
if (!hitObject.Path.HasValidLength)
|
if (!hitObject.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||||
hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(hitObject.Path.ControlPoints[i]) { Position = oldControlPoints[i] });
|
||||||
|
|
||||||
commandHandler.SafeSubmit(new MoveCommand(hitObject, oldPosition));
|
commandHandler.SafeSubmit(new MoveCommand(hitObject, oldPosition));
|
||||||
commandHandler.SafeSubmit(new SetStartTimeCommand(hitObject, oldStartTime));
|
commandHandler.SafeSubmit(new SetStartTimeCommand(hitObject, oldStartTime));
|
||||||
// Snap the path length again to undo the invalid length.
|
// Snap the path length again to undo the invalid length.
|
||||||
hitObject.SnapTo(distanceSnapProvider);
|
hitObject.SnapTo(distanceSnapProvider, commandHandler);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
||||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||||
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(hitObject.Path.ControlPoints[i]) { Type = dragPathTypes[i] });
|
||||||
|
|
||||||
EnsureValidPathTypes();
|
EnsureValidPathTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DragEnded() => changeHandler?.EndChange();
|
public void DragEnded() => commandHandler?.Commit();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -21,9 +21,11 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Commands;
|
||||||
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.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -34,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
||||||
|
|
||||||
|
protected SliderCommandProxy Proxy;
|
||||||
|
|
||||||
protected SliderBodyPiece BodyPiece { get; private set; } = null!;
|
protected SliderBodyPiece BodyPiece { get; private set; } = null!;
|
||||||
protected SliderCircleOverlay HeadOverlay { get; private set; } = null!;
|
protected SliderCircleOverlay HeadOverlay { get; private set; } = null!;
|
||||||
protected SliderCircleOverlay TailOverlay { get; private set; } = null!;
|
protected SliderCircleOverlay TailOverlay { get; private set; } = null!;
|
||||||
@ -50,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private EditorBeatmap? editorBeatmap { get; set; }
|
private EditorBeatmap? editorBeatmap { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IEditorChangeHandler? changeHandler { get; set; }
|
private EditorCommandHandler? commandHandler { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BindableBeatDivisor? beatDivisor { get; set; }
|
private BindableBeatDivisor? beatDivisor { get; set; }
|
||||||
@ -94,6 +98,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
Proxy = new SliderCommandProxy(commandHandler, HitObject);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
@ -216,7 +222,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// If there's more than two objects selected, ctrl+click should deselect
|
// If there's more than two objects selected, ctrl+click should deselect
|
||||||
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
|
if (e.ControlPressed && IsSelected && selectedObjects.Count < 2)
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
|
||||||
placementControlPoint = addControlPoint(e.MousePosition);
|
placementControlPoint = addControlPoint(e.MousePosition);
|
||||||
ControlPointVisualiser?.SetSelectionTo(placementControlPoint);
|
ControlPointVisualiser?.SetSelectionTo(placementControlPoint);
|
||||||
return true; // Stop input from being handled and modifying the selection
|
return true; // Stop input from being handled and modifying the selection
|
||||||
@ -244,13 +249,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1);
|
lengthAdjustMouseOffset = ToLocalSpace(e.ScreenSpaceMouseDownPosition) - HitObject.Position - HitObject.Path.PositionAt(1);
|
||||||
oldDuration = HitObject.Path.Distance / HitObject.SliderVelocityMultiplier;
|
oldDuration = HitObject.Path.Distance / HitObject.SliderVelocityMultiplier;
|
||||||
oldVelocityMultiplier = HitObject.SliderVelocityMultiplier;
|
oldVelocityMultiplier = HitObject.SliderVelocityMultiplier;
|
||||||
changeHandler?.BeginChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endAdjustLength()
|
private void endAdjustLength()
|
||||||
{
|
{
|
||||||
trimExcessControlPoints(HitObject.Path);
|
trimExcessControlPoints(Proxy.Path);
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
isAdjustingLength = false;
|
isAdjustingLength = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,8 +281,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier))
|
if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HitObject.SliderVelocityMultiplier = proposedVelocity;
|
Proxy.SliderVelocityMultiplier = proposedVelocity;
|
||||||
HitObject.Path.ExpectedDistance.Value = proposedDistance;
|
Proxy.Path.ExpectedDistance = proposedDistance;
|
||||||
editorBeatmap?.Update(HitObject);
|
editorBeatmap?.Update(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,9 +290,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
/// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
|
/// Trims control points from the end of the slider path which are not required to reach the expected end of the slider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sliderPath">The slider path to trim control points of.</param>
|
/// <param name="sliderPath">The slider path to trim control points of.</param>
|
||||||
private void trimExcessControlPoints(SliderPath sliderPath)
|
private void trimExcessControlPoints(SliderPathCommandProxy sliderPath)
|
||||||
{
|
{
|
||||||
if (!sliderPath.ExpectedDistance.Value.HasValue)
|
if (!sliderPath.ExpectedDistance.HasValue)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
|
double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
|
||||||
@ -382,7 +386,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
ControlPointVisualiser?.DragEnded();
|
ControlPointVisualiser?.DragEnded();
|
||||||
|
|
||||||
placementControlPoint = null;
|
placementControlPoint = null;
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,12 +439,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
var pathControlPoint = new PathControlPoint { Position = position };
|
var pathControlPoint = new PathControlPoint { Position = position };
|
||||||
|
|
||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
Proxy.Path.ControlPoints.Insert(insertionIndex, pathControlPoint);
|
||||||
controlPoints.Insert(insertionIndex, pathControlPoint);
|
|
||||||
|
|
||||||
ControlPointVisualiser?.EnsureValidPathTypes();
|
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||||
|
|
||||||
HitObject.SnapTo(distanceSnapProvider);
|
HitObject.SnapTo(distanceSnapProvider, commandHandler);
|
||||||
|
|
||||||
return pathControlPoint;
|
return pathControlPoint;
|
||||||
}
|
}
|
||||||
@ -456,15 +459,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// The first control point in the slider must have a type, so take it from the previous "first" one
|
// The first control point in the slider must have a type, so take it from the previous "first" one
|
||||||
// Todo: Should be handled within SliderPath itself
|
// Todo: Should be handled within SliderPath itself
|
||||||
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type == null)
|
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type == null)
|
||||||
controlPoints[1].Type = controlPoints[0].Type;
|
new PathControlPointCommandProxy(commandHandler, c).Type = controlPoints[0].Type;
|
||||||
|
|
||||||
controlPoints.Remove(c);
|
Proxy.Path.ControlPoints.Remove(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlPointVisualiser?.EnsureValidPathTypes();
|
ControlPointVisualiser?.EnsureValidPathTypes();
|
||||||
|
|
||||||
// Snap the slider to the current beat divisor before checking length validity.
|
// Snap the slider to the current beat divisor before checking length validity.
|
||||||
HitObject.SnapTo(distanceSnapProvider);
|
HitObject.SnapTo(distanceSnapProvider, commandHandler);
|
||||||
|
|
||||||
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
|
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
|
||||||
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
|
||||||
@ -477,8 +480,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
||||||
Vector2 first = controlPoints[0].Position;
|
Vector2 first = controlPoints[0].Position;
|
||||||
foreach (var c in controlPoints)
|
foreach (var c in controlPoints)
|
||||||
c.Position -= first;
|
commandHandler.SafeSubmit(new UpdateControlPointCommand(c) { Position = c.Position - first });
|
||||||
HitObject.Position += first;
|
|
||||||
|
commandHandler.SafeSubmit(new MoveCommand(HitObject, HitObject.Position + first));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void splitControlPoints(List<PathControlPoint> controlPointsToSplitAt)
|
private void splitControlPoints(List<PathControlPoint> controlPointsToSplitAt)
|
||||||
@ -495,6 +499,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
editorBeatmap.SelectedHitObjects.Clear();
|
editorBeatmap.SelectedHitObjects.Clear();
|
||||||
|
|
||||||
|
var controlPointsProxy = new PathControlPointsCommandProxy(commandHandler, controlPoints);
|
||||||
|
|
||||||
foreach (var splitPoint in controlPointsToSplitAt)
|
foreach (var splitPoint in controlPointsToSplitAt)
|
||||||
{
|
{
|
||||||
if (splitPoint == controlPoints[0] || splitPoint == controlPoints[^1] || splitPoint.Type == null)
|
if (splitPoint == controlPoints[0] || splitPoint == controlPoints[^1] || splitPoint.Type == null)
|
||||||
@ -508,7 +514,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
// Extract the split portion and remove from the original slider.
|
// Extract the split portion and remove from the original slider.
|
||||||
var splitControlPoints = controlPoints.Take(index + 1).ToList();
|
var splitControlPoints = controlPoints.Take(index + 1).ToList();
|
||||||
controlPoints.RemoveRange(0, index);
|
controlPointsProxy.RemoveRange(0, index);
|
||||||
|
|
||||||
var newSlider = new Slider
|
var newSlider = new Slider
|
||||||
{
|
{
|
||||||
@ -521,28 +527,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
Path = new SliderPath(splitControlPoints.Select(o => new PathControlPoint(o.Position - splitControlPoints[0].Position, o == splitControlPoints[^1] ? null : o.Type)).ToArray())
|
Path = new SliderPath(splitControlPoints.Select(o => new PathControlPoint(o.Position - splitControlPoints[0].Position, o == splitControlPoints[^1] ? null : o.Type)).ToArray())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Proxy.StartTime += split_gap;
|
||||||
|
|
||||||
// Increase the start time of the slider before adding the new slider so the new slider is immediately inserted at the correct index and internal state remains valid.
|
// Increase the start time of the slider before adding the new slider so the new slider is immediately inserted at the correct index and internal state remains valid.
|
||||||
HitObject.StartTime += split_gap;
|
commandHandler.SafeSubmit(new AddHitObjectCommand(editorBeatmap, newSlider));
|
||||||
|
|
||||||
editorBeatmap.Add(newSlider);
|
Proxy.NewCombo = false;
|
||||||
|
Proxy.Path.ExpectedDistance -= newSlider.Path.CalculatedDistance;
|
||||||
HitObject.NewCombo = false;
|
Proxy.StartTime += newSlider.SpanDuration;
|
||||||
HitObject.Path.ExpectedDistance.Value -= newSlider.Path.CalculatedDistance;
|
|
||||||
HitObject.StartTime += newSlider.SpanDuration;
|
|
||||||
|
|
||||||
// In case the remainder of the slider has no length left over, give it length anyways so we don't get a 0 length slider.
|
// In case the remainder of the slider has no length left over, give it length anyways so we don't get a 0 length slider.
|
||||||
if (HitObject.Path.ExpectedDistance.Value <= Precision.DOUBLE_EPSILON)
|
if (HitObject.Path.ExpectedDistance.Value <= Precision.DOUBLE_EPSILON)
|
||||||
{
|
{
|
||||||
HitObject.Path.ExpectedDistance.Value = null;
|
Proxy.Path.ExpectedDistance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once all required pieces have been split off, the original slider has the final split.
|
// Once all required pieces have been split off, the original slider has the final split.
|
||||||
// As a final step, we must reset its control points to have an origin of (0,0).
|
// As a final step, we must reset its control points to have an origin of (0,0).
|
||||||
Vector2 first = controlPoints[0].Position;
|
Vector2 first = controlPoints[0].Position;
|
||||||
foreach (var c in controlPoints)
|
foreach (var c in controlPointsProxy)
|
||||||
c.Position -= first;
|
c.Position -= first;
|
||||||
HitObject.Position += first;
|
|
||||||
|
Proxy.Position += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void convertToStream()
|
private void convertToStream()
|
||||||
@ -553,8 +560,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
|
||||||
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
double time = HitObject.StartTime;
|
double time = HitObject.StartTime;
|
||||||
|
|
||||||
@ -570,30 +575,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
|
||||||
|
|
||||||
editorBeatmap.Add(new HitCircle
|
commandHandler.SafeSubmit(new AddHitObjectCommand(editorBeatmap, new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = time,
|
StartTime = time,
|
||||||
Position = position,
|
Position = position,
|
||||||
NewCombo = i == 0 && HitObject.NewCombo,
|
NewCombo = i == 0 && HitObject.NewCombo,
|
||||||
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
|
||||||
});
|
}));
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
time = HitObject.StartTime + i * streamSpacing;
|
time = HitObject.StartTime + i * streamSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
editorBeatmap.Remove(HitObject);
|
commandHandler.SafeSubmit(new RemoveHitObjectCommand(editorBeatmap, HitObject));
|
||||||
|
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
public override MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
{
|
{
|
||||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () =>
|
new OsuMenuItem("Add control point", MenuItemType.Standard, () =>
|
||||||
{
|
{
|
||||||
changeHandler?.BeginChange();
|
|
||||||
addControlPoint(lastRightClickPosition);
|
addControlPoint(lastRightClickPosition);
|
||||||
changeHandler?.EndChange();
|
commandHandler?.Commit();
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft))
|
Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft))
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
// 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 osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class AddControlPointCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public readonly BindableList<PathControlPoint> ControlPoints;
|
||||||
|
|
||||||
|
public readonly int InsertionIndex;
|
||||||
|
|
||||||
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
|
public AddControlPointCommand(BindableList<PathControlPoint> controlPoints, int insertionIndex, PathControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
ControlPoints = controlPoints;
|
||||||
|
InsertionIndex = insertionIndex;
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply() => ControlPoints.Insert(InsertionIndex, ControlPoint);
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo() => new RemoveControlPointCommand(ControlPoints, InsertionIndex);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class OsuHitObjectCommandProxy : HitObjectCommandProxy
|
||||||
|
{
|
||||||
|
public OsuHitObjectCommandProxy(EditorCommandHandler? commandHandler, OsuHitObject hitObject)
|
||||||
|
: base(commandHandler, hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected new OsuHitObject HitObject => (OsuHitObject)base.HitObject;
|
||||||
|
|
||||||
|
public Vector2 Position
|
||||||
|
{
|
||||||
|
get => HitObject.Position;
|
||||||
|
set => Submit(new MoveCommand(HitObject, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NewCombo
|
||||||
|
{
|
||||||
|
get => HitObject.NewCombo;
|
||||||
|
set => Submit(new SetNewComboCommand(HitObject, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class PathControlPointCommandProxy : CommandProxy
|
||||||
|
{
|
||||||
|
public PathControlPointCommandProxy(EditorCommandHandler? commandHandler, PathControlPoint controlPoint)
|
||||||
|
: base(commandHandler)
|
||||||
|
{
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
|
public Vector2 Position
|
||||||
|
{
|
||||||
|
get => ControlPoint.Position;
|
||||||
|
set => Submit(new UpdateControlPointCommand(ControlPoint) { Position = Position });
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathType? Type
|
||||||
|
{
|
||||||
|
get => ControlPoint.Type;
|
||||||
|
set => Submit(new UpdateControlPointCommand(ControlPoint) { Type = Type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class PathControlPointsCommandProxy : CommandProxy, IList<PathControlPointCommandProxy>
|
||||||
|
{
|
||||||
|
public PathControlPointsCommandProxy(EditorCommandHandler? commandHandler, BindableList<PathControlPoint> controlPoints)
|
||||||
|
: base(commandHandler)
|
||||||
|
{
|
||||||
|
ControlPoints = controlPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly BindableList<PathControlPoint> ControlPoints;
|
||||||
|
|
||||||
|
public int IndexOf(PathControlPointCommandProxy item)
|
||||||
|
{
|
||||||
|
return ControlPoints.IndexOf(item.ControlPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, PathControlPointCommandProxy item) => Insert(index, item.ControlPoint);
|
||||||
|
|
||||||
|
public void Insert(int index, PathControlPoint controlPoint) => Submit(new AddControlPointCommand(ControlPoints, index, controlPoint));
|
||||||
|
|
||||||
|
public void RemoveAt(int index) => Submit(new RemoveControlPointCommand(ControlPoints, index));
|
||||||
|
|
||||||
|
public PathControlPointCommandProxy this[int index]
|
||||||
|
{
|
||||||
|
get => new PathControlPointCommandProxy(CommandHandler, ControlPoints[index]);
|
||||||
|
set => Submit(new AddControlPointCommand(ControlPoints, index, value.ControlPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRange(int index, int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
Submit(new RemoveControlPointCommand(ControlPoints, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(PathControlPointCommandProxy item) => Add(item.ControlPoint);
|
||||||
|
|
||||||
|
public void Add(PathControlPoint controlPoint) => Submit(new AddControlPointCommand(ControlPoints, ControlPoints.Count, controlPoint));
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
while (ControlPoints.Count > 0)
|
||||||
|
Remove(ControlPoints[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(PathControlPointCommandProxy item)
|
||||||
|
{
|
||||||
|
return ControlPoints.Any(c => c.Equals(item.ControlPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(PathControlPointCommandProxy[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
||||||
|
array[arrayIndex + i] = new PathControlPointCommandProxy(CommandHandler, ControlPoints[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(PathControlPointCommandProxy item) => Remove(item.ControlPoint);
|
||||||
|
|
||||||
|
public bool Remove(PathControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
if (!ControlPoints.Contains(controlPoint))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Submit(new RemoveControlPointCommand(ControlPoints, controlPoint));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => ControlPoints.Count;
|
||||||
|
|
||||||
|
public bool IsReadOnly => ControlPoints.IsReadOnly;
|
||||||
|
|
||||||
|
public IEnumerator<PathControlPointCommandProxy> GetEnumerator() => new PathControlPointsCommandProxyEnumerator(CommandHandler, ControlPoints.GetEnumerator());
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
private readonly struct PathControlPointsCommandProxyEnumerator : IEnumerator<PathControlPointCommandProxy>
|
||||||
|
{
|
||||||
|
public PathControlPointsCommandProxyEnumerator(
|
||||||
|
EditorCommandHandler? commandHandler,
|
||||||
|
IEnumerator<PathControlPoint> enumerator
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.commandHandler = commandHandler;
|
||||||
|
this.enumerator = enumerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly EditorCommandHandler? commandHandler;
|
||||||
|
|
||||||
|
private readonly IEnumerator<PathControlPoint> enumerator;
|
||||||
|
|
||||||
|
public bool MoveNext() => enumerator.MoveNext();
|
||||||
|
|
||||||
|
public void Reset() => enumerator.Reset();
|
||||||
|
|
||||||
|
public PathControlPointCommandProxy Current => new PathControlPointCommandProxy(commandHandler, enumerator.Current);
|
||||||
|
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
|
||||||
|
public void Dispose() => enumerator.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
// 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 osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class RemoveControlPointCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public readonly BindableList<PathControlPoint> ControlPoints;
|
||||||
|
|
||||||
|
public readonly int Index;
|
||||||
|
|
||||||
|
public RemoveControlPointCommand(BindableList<PathControlPoint> controlPoints, int index)
|
||||||
|
{
|
||||||
|
ControlPoints = controlPoints;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoveControlPointCommand(BindableList<PathControlPoint> controlPoints, PathControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
ControlPoints = controlPoints;
|
||||||
|
Index = controlPoints.IndexOf(controlPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply() => ControlPoints.RemoveAt(Index);
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo() => new AddControlPointCommand(ControlPoints, Index, ControlPoints[Index]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class SetSliderVelocityMultiplierCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public readonly Slider Slider;
|
||||||
|
|
||||||
|
public readonly double SliderVelocityMultiplier;
|
||||||
|
|
||||||
|
public SetSliderVelocityMultiplierCommand(Slider slider, double sliderVelocityMultiplier)
|
||||||
|
{
|
||||||
|
Slider = slider;
|
||||||
|
SliderVelocityMultiplier = sliderVelocityMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply() => Slider.SliderVelocityMultiplier = SliderVelocityMultiplier;
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo() => new SetSliderVelocityMultiplierCommand(Slider, Slider.SliderVelocityMultiplier);
|
||||||
|
}
|
||||||
|
}
|
27
osu.Game.Rulesets.Osu/Edit/Commands/SetNewComboCommand.cs
Normal file
27
osu.Game.Rulesets.Osu/Edit/Commands/SetNewComboCommand.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class SetNewComboCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public OsuHitObject Target;
|
||||||
|
|
||||||
|
public bool NewCombo;
|
||||||
|
|
||||||
|
public SetNewComboCommand(OsuHitObject target, bool newCombo)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
NewCombo = newCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply() => Target.NewCombo = NewCombo;
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo() => new SetNewComboCommand(Target, Target.NewCombo);
|
||||||
|
|
||||||
|
public bool IsRedundant => NewCombo == Target.NewCombo;
|
||||||
|
}
|
||||||
|
}
|
26
osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs
Normal file
26
osu.Game.Rulesets.Osu/Edit/Commands/SliderCommandProxy.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class SliderCommandProxy : OsuHitObjectCommandProxy
|
||||||
|
{
|
||||||
|
public SliderCommandProxy(EditorCommandHandler? commandHandler, Slider hitObject)
|
||||||
|
: base(commandHandler, hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected new Slider HitObject => (Slider)base.HitObject;
|
||||||
|
|
||||||
|
public SliderPathCommandProxy Path => new SliderPathCommandProxy(CommandHandler, HitObject.Path);
|
||||||
|
|
||||||
|
public double SliderVelocityMultiplier
|
||||||
|
{
|
||||||
|
get => HitObject.SliderVelocityMultiplier;
|
||||||
|
set => Submit(new SetSliderVelocityMultiplierCommand(HitObject, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class SliderPathCommandProxy : CommandProxy
|
||||||
|
{
|
||||||
|
public SliderPathCommandProxy(EditorCommandHandler? commandHandler, SliderPath path)
|
||||||
|
: base(commandHandler)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly SliderPath Path;
|
||||||
|
|
||||||
|
public double? ExpectedDistance
|
||||||
|
{
|
||||||
|
get => Path.ExpectedDistance.Value;
|
||||||
|
set => Submit(new SetExpectedDistanceCommand(Path, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathControlPointsCommandProxy ControlPoints => new PathControlPointsCommandProxy(CommandHandler, Path.ControlPoints);
|
||||||
|
|
||||||
|
public IEnumerable<double> GetSegmentEnds() => Path.GetSegmentEnds();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Commands
|
||||||
|
{
|
||||||
|
public class UpdateControlPointCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public PathControlPoint ControlPoint;
|
||||||
|
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
public PathType? Type;
|
||||||
|
|
||||||
|
public UpdateControlPointCommand(PathControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
Position = controlPoint.Position;
|
||||||
|
Type = controlPoint.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateControlPointCommand(PathControlPoint controlPoint, Vector2 position, PathType? type)
|
||||||
|
{
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
Position = position;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply()
|
||||||
|
{
|
||||||
|
ControlPoint.Position = Position;
|
||||||
|
ControlPoint.Type = Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo()
|
||||||
|
{
|
||||||
|
return new UpdateControlPointCommand(ControlPoint, ControlPoint.Position, ControlPoint.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Commands;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects
|
namespace osu.Game.Rulesets.Objects
|
||||||
@ -14,10 +16,12 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Snaps the provided <paramref name="hitObject"/>'s duration using the <paramref name="snapProvider"/>.
|
/// Snaps the provided <paramref name="hitObject"/>'s duration using the <paramref name="snapProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void SnapTo<THitObject>(this THitObject hitObject, IDistanceSnapProvider? snapProvider)
|
public static void SnapTo<THitObject>(this THitObject hitObject, IDistanceSnapProvider? snapProvider, EditorCommandHandler? commandHandler = null)
|
||||||
where THitObject : HitObject, IHasPath
|
where THitObject : HitObject, IHasPath
|
||||||
{
|
{
|
||||||
hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance;
|
double distance = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance;
|
||||||
|
|
||||||
|
commandHandler.SafeSubmit(new SetExpectedDistanceCommand(hitObject.Path, distance));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
21
osu.Game/Screens/Edit/Commands/CommandProxy.cs
Normal file
21
osu.Game/Screens/Edit/Commands/CommandProxy.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Commands
|
||||||
|
{
|
||||||
|
public abstract class CommandProxy
|
||||||
|
{
|
||||||
|
protected EditorCommandHandler? CommandHandler;
|
||||||
|
|
||||||
|
protected CommandProxy(EditorCommandHandler? commandHandler)
|
||||||
|
{
|
||||||
|
CommandHandler = commandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Submit(IEditorCommand command) => CommandHandler.SafeSubmit(command);
|
||||||
|
|
||||||
|
protected void Submit(IEnumerable<IEditorCommand> command) => CommandHandler.SafeSubmit(command);
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs
Normal file
24
osu.Game/Screens/Edit/Commands/HitObjectCommandProxy.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Commands
|
||||||
|
{
|
||||||
|
public class HitObjectCommandProxy : CommandProxy
|
||||||
|
{
|
||||||
|
public HitObjectCommandProxy(EditorCommandHandler? commandHandler, HitObject hitObject)
|
||||||
|
: base(commandHandler)
|
||||||
|
{
|
||||||
|
HitObject = hitObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HitObject HitObject;
|
||||||
|
|
||||||
|
public double StartTime
|
||||||
|
{
|
||||||
|
get => HitObject.StartTime;
|
||||||
|
set => Submit(new SetStartTimeCommand(HitObject, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game/Screens/Edit/Commands/SetExpectedDistanceCommand.cs
Normal file
24
osu.Game/Screens/Edit/Commands/SetExpectedDistanceCommand.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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 osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Commands
|
||||||
|
{
|
||||||
|
public class SetExpectedDistanceCommand : IEditorCommand
|
||||||
|
{
|
||||||
|
public readonly SliderPath Path;
|
||||||
|
|
||||||
|
public readonly double? ExpectedDistance;
|
||||||
|
|
||||||
|
public SetExpectedDistanceCommand(SliderPath path, double? expectedDistance)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
ExpectedDistance = expectedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Apply() => Path.ExpectedDistance.Value = ExpectedDistance;
|
||||||
|
|
||||||
|
public IEditorCommand CreateUndo() => new SetExpectedDistanceCommand(Path, Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Screens.Edit.Commands;
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -47,11 +48,16 @@ namespace osu.Game.Screens.Edit
|
|||||||
public bool Commit()
|
public bool Commit()
|
||||||
{
|
{
|
||||||
if (!HasUncommittedChanges)
|
if (!HasUncommittedChanges)
|
||||||
|
{
|
||||||
|
Logger.Log("Nothing to commit");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
undoStack.Push(currentTransaction);
|
undoStack.Push(currentTransaction);
|
||||||
redoStack.Clear();
|
redoStack.Clear();
|
||||||
|
|
||||||
|
Logger.Log($"Added {currentTransaction.Entries.Count} command(s) to undo stack");
|
||||||
|
|
||||||
currentTransaction = new Transaction();
|
currentTransaction = new Transaction();
|
||||||
|
|
||||||
historyChanged();
|
historyChanged();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Game.Screens.Edit.Commands;
|
using osu.Game.Screens.Edit.Commands;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -14,5 +15,16 @@ namespace osu.Game.Screens.Edit
|
|||||||
else
|
else
|
||||||
command.Apply();
|
command.Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SafeSubmit(this EditorCommandHandler? manager, IEnumerable<IEditorCommand> commands, bool commitImmediately = false)
|
||||||
|
{
|
||||||
|
if (manager != null)
|
||||||
|
manager.Submit(commands, commitImmediately);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var command in commands)
|
||||||
|
command.Apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user