mirror of
https://github.com/ppy/osu.git
synced 2025-02-21 03:02:54 +08:00
Merge branch 'editor-position-snap' into mania-distance-snap-grid
This commit is contained in:
commit
94bb59db0d
@ -50,5 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||||
|
|
||||||
public ManiaPlayfield Playfield => null;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
|
public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,5 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
||||||
|
|
||||||
public ManiaPlayfield Playfield => null;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
|
public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,5 +69,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public Column ColumnAt(Vector2 screenSpacePosition) => null;
|
public Column ColumnAt(Vector2 screenSpacePosition) => null;
|
||||||
|
|
||||||
public ManiaPlayfield Playfield { get; }
|
public ManiaPlayfield Playfield { get; }
|
||||||
|
|
||||||
|
public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -17,6 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
private readonly EditNotePiece headPiece;
|
private readonly EditNotePiece headPiece;
|
||||||
private readonly EditNotePiece tailPiece;
|
private readonly EditNotePiece tailPiece;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IManiaHitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
public HoldNotePlacementBlueprint()
|
public HoldNotePlacementBlueprint()
|
||||||
: base(new HoldNote())
|
: base(new HoldNote())
|
||||||
{
|
{
|
||||||
@ -36,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
if (Column != null)
|
if (Column != null)
|
||||||
{
|
{
|
||||||
headPiece.Y = PositionAt(HitObject.StartTime);
|
headPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.StartTime, Column)).Y;
|
||||||
tailPiece.Y = PositionAt(HitObject.EndTime);
|
tailPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.EndTime, Column)).Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y));
|
||||||
@ -59,23 +64,28 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdatePosition(screenSpacePosition);
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
if (PlacementActive)
|
if (PlacementActive)
|
||||||
{
|
{
|
||||||
var endTime = TimeAt(screenSpacePosition);
|
if (result.Time is double endTime)
|
||||||
|
{
|
||||||
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
||||||
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
headPiece.Width = tailPiece.Width = SnappedWidth;
|
if (result is ManiaSnapResult maniaResult)
|
||||||
headPiece.X = tailPiece.X = SnappedMousePosition.X;
|
{
|
||||||
|
headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth;
|
||||||
|
headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||||
|
}
|
||||||
|
|
||||||
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
|
if (result.Time is double startTime)
|
||||||
|
originalStartTime = HitObject.StartTime = startTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,34 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint,
|
public abstract class ManiaPlacementBlueprint<T> : PlacementBlueprint
|
||||||
IRequireHighFrequencyMousePosition // the playfield could be moving behind us
|
|
||||||
where T : ManiaHitObject
|
where T : ManiaHitObject
|
||||||
{
|
{
|
||||||
protected new T HitObject => (T)base.HitObject;
|
protected new T HitObject => (T)base.HitObject;
|
||||||
|
|
||||||
protected Column Column;
|
private Column column;
|
||||||
|
|
||||||
/// <summary>
|
public Column Column
|
||||||
/// The current mouse position, snapped to the closest column.
|
{
|
||||||
/// </summary>
|
get => column;
|
||||||
protected Vector2 SnappedMousePosition { get; private set; }
|
set
|
||||||
|
{
|
||||||
|
if (value == column)
|
||||||
|
return;
|
||||||
|
|
||||||
/// <summary>
|
column = value;
|
||||||
/// The width of the closest column to the current mouse position.
|
HitObject.Column = column.Index;
|
||||||
/// </summary>
|
}
|
||||||
protected float SnappedWidth { get; private set; }
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IManiaHitObjectComposer composer { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IScrollingInfo scrollingInfo { get; set; }
|
|
||||||
|
|
||||||
protected ManiaPlacementBlueprint(T hitObject)
|
protected ManiaPlacementBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
@ -51,105 +42,16 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (Column == null)
|
if (Column == null)
|
||||||
return base.OnMouseDown(e);
|
return false;
|
||||||
|
|
||||||
HitObject.Column = Column.Index;
|
BeginPlacement(true);
|
||||||
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
if (!PlacementActive)
|
if (!PlacementActive)
|
||||||
Column = ColumnAt(screenSpacePosition);
|
Column = (result as ManiaSnapResult)?.Column;
|
||||||
|
|
||||||
if (Column == null) return;
|
|
||||||
|
|
||||||
SnappedWidth = Column.DrawWidth;
|
|
||||||
|
|
||||||
// Snap to the column
|
|
||||||
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
|
|
||||||
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected double TimeAt(Vector2 screenSpacePosition)
|
|
||||||
{
|
|
||||||
if (Column == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var hitObjectContainer = Column.HitObjectContainer;
|
|
||||||
|
|
||||||
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
|
||||||
// so we need to flip the vertical coordinate in the hitobject container's space
|
|
||||||
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
|
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
|
||||||
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
|
|
||||||
|
|
||||||
return scrollingInfo.Algorithm.TimeAt(hitObjectPos,
|
|
||||||
EditorClock.CurrentTime,
|
|
||||||
scrollingInfo.TimeRange.Value,
|
|
||||||
hitObjectContainer.DrawHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected float PositionAt(double time)
|
|
||||||
{
|
|
||||||
var pos = scrollingInfo.Algorithm.PositionAt(time,
|
|
||||||
EditorClock.CurrentTime,
|
|
||||||
scrollingInfo.TimeRange.Value,
|
|
||||||
Column.HitObjectContainer.DrawHeight);
|
|
||||||
|
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
|
||||||
pos = Column.HitObjectContainer.DrawHeight - pos;
|
|
||||||
|
|
||||||
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Column ColumnAt(Vector2 screenSpacePosition)
|
|
||||||
=> composer.ColumnAt(screenSpacePosition);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a mouse position to a hitobject position.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="mousePosition">The mouse position.</param>
|
|
||||||
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
|
|
||||||
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
|
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mousePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts a hitobject position to a mouse position.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObjectPosition">The hitobject position.</param>
|
|
||||||
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
|
|
||||||
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
|
|
||||||
{
|
|
||||||
switch (scrollingInfo.Direction.Value)
|
|
||||||
{
|
|
||||||
case ScrollingDirection.Up:
|
|
||||||
hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollingDirection.Down:
|
|
||||||
hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hitObjectPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -11,22 +12,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
|
public class NotePlacementBlueprint : ManiaPlacementBlueprint<Note>
|
||||||
{
|
{
|
||||||
|
private readonly EditNotePiece piece;
|
||||||
|
|
||||||
public NotePlacementBlueprint()
|
public NotePlacementBlueprint()
|
||||||
: base(new Note())
|
: base(new Note())
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Y;
|
InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre };
|
||||||
|
|
||||||
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.Update();
|
base.UpdatePosition(result);
|
||||||
|
|
||||||
Width = SnappedWidth;
|
if (result is ManiaSnapResult maniaResult)
|
||||||
Position = SnappedMousePosition;
|
{
|
||||||
|
piece.Width = maniaResult.Column.DrawWidth;
|
||||||
|
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
Column ColumnAt(Vector2 screenSpacePosition);
|
Column ColumnAt(Vector2 screenSpacePosition);
|
||||||
|
|
||||||
ManiaPlayfield Playfield { get; }
|
ManiaPlayfield Playfield { get; }
|
||||||
|
|
||||||
|
Vector2 ScreenSpacePositionAtTime(double time, Column column = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,31 +75,61 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var placementTime = GetSnappedPosition(ToLocalSpace(inputManager.CurrentState.Mouse.Position), 0).time;
|
var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||||
beatSnapGrid.SelectionTimeRange = (placementTime, placementTime);
|
if (result.Time is double time)
|
||||||
|
beatSnapGrid.SelectionTimeRange = (time, time);
|
||||||
|
else
|
||||||
|
beatSnapGrid.SelectionTimeRange = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
var hoc = Playfield.GetColumn(0).HitObjectContainer;
|
var column = ColumnAt(screenSpacePosition);
|
||||||
|
|
||||||
float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y;
|
if (column == null)
|
||||||
|
return new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down)
|
var hoc = column.HitObjectContainer;
|
||||||
|
|
||||||
|
// convert to local space of column so we can snap and fetch correct location.
|
||||||
|
Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition);
|
||||||
|
|
||||||
|
var scrollInfo = drawableRuleset.ScrollingInfo;
|
||||||
|
|
||||||
|
if (scrollInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
{
|
{
|
||||||
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
|
||||||
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
|
||||||
// so when scrolling downwards the coordinates need to be flipped.
|
// so when scrolling downwards the coordinates need to be flipped.
|
||||||
targetPosition = hoc.DrawHeight - targetPosition;
|
localPosition.Y = hoc.DrawHeight - localPosition.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition,
|
double targetTime = scrollInfo.Algorithm.TimeAt(localPosition.Y, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight);
|
||||||
EditorClock.CurrentTime,
|
|
||||||
drawableRuleset.ScrollingInfo.TimeRange.Value,
|
|
||||||
hoc.DrawHeight);
|
|
||||||
|
|
||||||
return base.GetSnappedPosition(position, targetTime);
|
// apply beat snapping
|
||||||
|
targetTime = BeatSnapProvider.SnapTime(targetTime);
|
||||||
|
|
||||||
|
// convert back to screen space
|
||||||
|
screenSpacePosition = ScreenSpacePositionAtTime(targetTime, column);
|
||||||
|
|
||||||
|
return new ManiaSnapResult(screenSpacePosition, targetTime, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 ScreenSpacePositionAtTime(double time, Column column = null)
|
||||||
|
{
|
||||||
|
var hoc = (column ?? Playfield.GetColumn(0)).HitObjectContainer;
|
||||||
|
var scrollInfo = drawableRuleset.ScrollingInfo;
|
||||||
|
|
||||||
|
var pos = scrollInfo.Algorithm.PositionAt(time, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight);
|
||||||
|
|
||||||
|
if (scrollInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
// as explained above
|
||||||
|
pos = hoc.DrawHeight - pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hoc.ToScreenSpace(new Vector2(hoc.DrawWidth / 2, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||||
|
20
osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs
Normal file
20
osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit
|
||||||
|
{
|
||||||
|
public class ManiaSnapResult : SnapResult
|
||||||
|
{
|
||||||
|
public readonly Column Column;
|
||||||
|
|
||||||
|
public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column)
|
||||||
|
: base(screenSpacePosition, time)
|
||||||
|
{
|
||||||
|
Column = column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||||
|
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
private TestOsuDistanceSnapGrid grid;
|
private TestOsuDistanceSnapGrid grid;
|
||||||
@ -172,9 +172,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SnapProvider : IDistanceSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||||
@ -40,6 +39,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition);
|
public override void UpdatePosition(SnapResult result) => HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private IEditorChangeHandler changeHandler { get; set; }
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IPositionSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||||
{
|
{
|
||||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition);
|
||||||
Vector2 movementDelta = snappedPosition - slider.Position;
|
Vector2 movementDelta = (result?.ScreenSpacePosition ?? e.MousePosition) - slider.Position;
|
||||||
|
|
||||||
slider.Position += movementDelta;
|
slider.Position += movementDelta;
|
||||||
slider.StartTime = snappedTime;
|
slider.StartTime = result?.Time ?? slider.StartTime;
|
||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case PlacementState.Initial:
|
case PlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
HitObject.Position = ToLocalSpace(screenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
|
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdatePosition(Vector2 screenSpacePosition)
|
public override void UpdatePosition(SnapResult result)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Cached(typeof(EditorBeatmap))]
|
[Cached(typeof(EditorBeatmap))]
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
public TestSceneDistanceSnapGrid()
|
public TestSceneDistanceSnapGrid()
|
||||||
@ -151,9 +151,9 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
=> (Vector2.Zero, 0);
|
=> (Vector2.Zero, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SnapProvider : IDistanceSnapProvider
|
private class SnapProvider : IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
private IAdjustableClock adjustableClock { get; set; }
|
private IAdjustableClock adjustableClock { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
|
||||||
|
|
||||||
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
|
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
|
||||||
|
|
||||||
@ -245,8 +245,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
EditorBeatmap.PlacementObject.Value = hitObject;
|
EditorBeatmap.PlacementObject.Value = hitObject;
|
||||||
|
|
||||||
if (distanceSnapGrid != null)
|
hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time ?? EditorClock.CurrentTime;
|
||||||
hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndPlacement(HitObject hitObject, bool commit)
|
public void EndPlacement(HitObject hitObject, bool commit)
|
||||||
@ -265,40 +264,48 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
|
||||||
|
|
||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time);
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null);
|
||||||
|
|
||||||
|
// TODO: move distance snap grid to OsuHitObjectComposer.
|
||||||
|
(Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition));
|
||||||
|
|
||||||
|
return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time);
|
||||||
|
}
|
||||||
|
|
||||||
public override float GetBeatSnapDistanceAt(double referenceTime)
|
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||||
{
|
{
|
||||||
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime);
|
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime);
|
||||||
return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatSnapProvider.BeatDivisor);
|
return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float DurationToDistance(double referenceTime, double duration)
|
public override float DurationToDistance(double referenceTime, double duration)
|
||||||
{
|
{
|
||||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
||||||
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime));
|
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double DistanceToDuration(double referenceTime, float distance)
|
public override double DistanceToDuration(double referenceTime, float distance)
|
||||||
{
|
{
|
||||||
double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime);
|
||||||
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength;
|
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||||
=> beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
|
=> BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
|
||||||
|
|
||||||
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
||||||
{
|
{
|
||||||
var snappedEndTime = beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime);
|
var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime);
|
||||||
|
|
||||||
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Cached(typeof(HitObjectComposer))]
|
[Cached(typeof(HitObjectComposer))]
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider
|
public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
internal HitObjectComposer()
|
internal HitObjectComposer()
|
||||||
{
|
{
|
||||||
@ -323,7 +330,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
||||||
|
|
||||||
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||||
|
|
||||||
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
||||||
|
|
||||||
|
@ -5,9 +5,14 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
namespace osu.Game.Rulesets.Edit
|
||||||
{
|
{
|
||||||
public interface IDistanceSnapProvider
|
public interface IPositionSnapProvider
|
||||||
{
|
{
|
||||||
(Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
/// <summary>
|
||||||
|
/// Given a position, find a valid time snap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="screenSpacePosition">The screen-space position to be snapped.</param>
|
||||||
|
/// <returns>The time and position post-snapping.</returns>
|
||||||
|
SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
/// Retrieves the distance between two points within a timing point that are one beat length apart.
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
|
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
|
@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Signals that the placement of <see cref="HitObject"/> has started.
|
/// Signals that the placement of <see cref="HitObject"/> has started.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startTime">The start time of <see cref="HitObject"/> at the placement point. If null, the current clock time is used.</param>
|
|
||||||
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
/// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
|
||||||
protected void BeginPlacement(double? startTime = null, bool commitStart = false)
|
protected void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
|
// applies snapping to above time
|
||||||
placementHandler.BeginPlacement(HitObject);
|
placementHandler.BeginPlacement(HitObject);
|
||||||
|
|
||||||
PlacementActive |= commitStart;
|
PlacementActive |= commitStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +86,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="screenSpacePosition">The screen-space position.</param>
|
/// <param name="snapResult">The snap result information.</param>
|
||||||
public abstract void UpdatePosition(Vector2 screenSpacePosition);
|
public abstract void UpdatePosition(SnapResult snapResult);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty)"/>,
|
/// Invokes <see cref="Objects.HitObject.ApplyDefaults(ControlPointInfo,BeatmapDifficulty)"/>,
|
||||||
|
@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
|
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
|
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.
|
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.
|
||||||
|
29
osu.Game/Rulesets/Edit/SnapResult.cs
Normal file
29
osu.Game/Rulesets/Edit/SnapResult.cs
Normal file
@ -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 osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The result of a position/time snapping process.
|
||||||
|
/// </summary>
|
||||||
|
public class SnapResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The screen space position, potentially altered for snapping.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 ScreenSpacePosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The resultant time for snapping, if a value could be attained.
|
||||||
|
/// </summary>
|
||||||
|
public double? Time;
|
||||||
|
|
||||||
|
public SnapResult(Vector2 screenSpacePosition, double? time)
|
||||||
|
{
|
||||||
|
ScreenSpacePosition = screenSpacePosition;
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private IDistanceSnapProvider snapProvider { get; set; }
|
private IPositionSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
protected BlueprintContainer()
|
protected BlueprintContainer()
|
||||||
{
|
{
|
||||||
@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
foreach (var blueprint in SelectionBlueprints)
|
foreach (var blueprint in SelectionBlueprints)
|
||||||
{
|
{
|
||||||
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint))
|
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint))
|
||||||
blueprint.Select();
|
blueprint.Select();
|
||||||
else
|
else
|
||||||
blueprint.Deselect();
|
blueprint.Deselect();
|
||||||
@ -384,7 +384,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
|
||||||
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
|
||||||
movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct
|
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -405,16 +405,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
|
||||||
|
|
||||||
// Retrieve a snapped position.
|
// Retrieve a snapped position.
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime);
|
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
|
||||||
|
|
||||||
// Move the hitobjects.
|
// Move the hitobjects.
|
||||||
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition))))
|
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Apply the start time at the newly snapped-to position
|
if (result.Time.HasValue)
|
||||||
double offset = snappedTime - draggedObject.StartTime;
|
{
|
||||||
foreach (HitObject obj in selectionHandler.SelectedHitObjects)
|
// Apply the start time at the newly snapped-to position
|
||||||
obj.StartTime += offset;
|
double offset = result.Time.Value - draggedObject.StartTime;
|
||||||
|
foreach (HitObject obj in selectionHandler.SelectedHitObjects)
|
||||||
|
obj.StartTime += offset;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
@ -65,12 +64,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
createPlacement();
|
createPlacement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlacementPosition(Vector2 screenSpacePosition)
|
private void updatePlacementPosition()
|
||||||
{
|
{
|
||||||
Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position;
|
var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
|
||||||
Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition);
|
|
||||||
|
|
||||||
currentPlacement.UpdatePosition(snappedScreenSpacePosition);
|
currentPlacement.UpdatePosition(snapResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -85,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
removePlacement();
|
removePlacement();
|
||||||
|
|
||||||
if (currentPlacement != null)
|
if (currentPlacement != null)
|
||||||
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
|
updatePlacementPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
||||||
@ -117,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
placementBlueprintContainer.Child = currentPlacement = blueprint;
|
||||||
|
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
||||||
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
|
updatePlacementPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
protected OsuColour Colours { get; private set; }
|
protected OsuColour Colours { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected IDistanceSnapProvider SnapProvider { get; private set; }
|
protected IPositionSnapProvider SnapProvider { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; }
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
@ -17,9 +17,9 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IPositionSnapProvider))]
|
||||||
[Cached]
|
[Cached]
|
||||||
public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider
|
public class Timeline : ZoomableScrollContainer, IPositionSnapProvider
|
||||||
{
|
{
|
||||||
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
||||||
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
@ -181,11 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||||
|
|
||||||
public double GetTimeFromScreenSpacePosition(Vector2 position)
|
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 position) =>
|
||||||
=> getTimeFromPosition(Content.ToLocalSpace(position));
|
new SnapResult(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position))));
|
||||||
|
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) =>
|
|
||||||
(position, beatSnapProvider.SnapTime(getTimeFromPosition(position)));
|
|
||||||
|
|
||||||
private double getTimeFromPosition(Vector2 localPosition) =>
|
private double getTimeFromPosition(Vector2 localPosition) =>
|
||||||
(localPosition.X / Content.DrawWidth) * track.Length;
|
(localPosition.X / Content.DrawWidth) * track.Length;
|
||||||
|
@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
||||||
|
|
||||||
public class DragBar : Container
|
public class DragBar : Container
|
||||||
{
|
{
|
||||||
@ -275,32 +275,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
OnDragHandled?.Invoke(e);
|
OnDragHandled?.Invoke(e);
|
||||||
|
|
||||||
var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition);
|
if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time)
|
||||||
|
|
||||||
switch (hitObject)
|
|
||||||
{
|
{
|
||||||
case IHasRepeats repeatHitObject:
|
switch (hitObject)
|
||||||
// find the number of repeats which can fit in the requested time.
|
{
|
||||||
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
case IHasRepeats repeatHitObject:
|
||||||
var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
// find the number of repeats which can fit in the requested time.
|
||||||
|
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
||||||
|
var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
||||||
|
|
||||||
if (proposedCount == repeatHitObject.RepeatCount)
|
if (proposedCount == repeatHitObject.RepeatCount)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
repeatHitObject.RepeatCount = proposedCount;
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IHasEndTime endTimeHitObject:
|
case IHasEndTime endTimeHitObject:
|
||||||
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
||||||
|
|
||||||
if (endTimeHitObject.EndTime == snappedTime)
|
if (endTimeHitObject.EndTime == snappedTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
endTimeHitObject.EndTime = snappedTime;
|
endTimeHitObject.EndTime = snappedTime;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.UpdateHitObject(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmap.UpdateHitObject(hitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
currentBlueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position);
|
currentBlueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(Drawable drawable)
|
public override void Add(Drawable drawable)
|
||||||
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
if (drawable is PlacementBlueprint blueprint)
|
if (drawable is PlacementBlueprint blueprint)
|
||||||
{
|
{
|
||||||
blueprint.Show();
|
blueprint.Show();
|
||||||
blueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position);
|
blueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user