Integrate osu! distance snapping grid (#6539)

Integrate osu! distance snapping grid

Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
Dean Herbert 2019-10-21 19:54:49 +09:00 committed by GitHub
commit 6b50d2194a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 188 additions and 41 deletions

View File

@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public OsuDistanceSnapGrid(OsuHitObject hitObject) public OsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject, hitObject.StackedEndPosition) : base(hitObject, hitObject.StackedEndPosition)
{ {
Masking = true;
} }
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)

View File

@ -2,10 +2,12 @@
// 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 System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
@ -52,5 +54,31 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateBlueprintFor(hitObject); return base.CreateBlueprintFor(hitObject);
} }
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
{
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
{
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
else
{
double minTime = objects.Min(h => h.StartTime);
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
}
} }
} }

View File

@ -4,12 +4,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
@ -22,6 +24,7 @@ using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
@ -30,16 +33,20 @@ namespace osu.Game.Rulesets.Edit
where TObject : HitObject where TObject : HitObject
{ {
protected IRulesetConfigManager Config { get; private set; } protected IRulesetConfigManager Config { get; private set; }
protected EditorBeatmap<TObject> EditorBeatmap { get; private set; }
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
[Resolved]
protected IFrameBasedClock EditorClock { get; private set; }
private IWorkingBeatmap workingBeatmap; private IWorkingBeatmap workingBeatmap;
private Beatmap<TObject> playableBeatmap; private Beatmap<TObject> playableBeatmap;
private EditorBeatmap<TObject> editorBeatmap;
private IBeatmapProcessor beatmapProcessor; private IBeatmapProcessor beatmapProcessor;
private DrawableEditRulesetWrapper<TObject> drawableRulesetWrapper; private DrawableEditRulesetWrapper<TObject> drawableRulesetWrapper;
private BlueprintContainer blueprintContainer; private BlueprintContainer blueprintContainer;
private Container distanceSnapGridContainer;
private DistanceSnapGrid distanceSnapGrid;
private readonly List<Container> layerContainers = new List<Container>(); private readonly List<Container> layerContainers = new List<Container>();
private InputManager inputManager; private InputManager inputManager;
@ -67,11 +74,13 @@ namespace osu.Game.Rulesets.Edit
return; return;
} }
var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[]
layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; {
distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both },
new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }
});
var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(blueprintContainer = new BlueprintContainer());
layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer();
layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerBelowRuleset);
layerContainers.Add(layerAboveRuleset); layerContainers.Add(layerAboveRuleset);
@ -114,11 +123,13 @@ namespace osu.Game.Rulesets.Edit
}; };
toolboxCollection.Items = toolboxCollection.Items =
CompositionTools.Select(t => new RadioButton(t.Name, () => blueprintContainer.CurrentTool = t)) CompositionTools.Select(t => new RadioButton(t.Name, () => selectTool(t)))
.Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) .Prepend(new RadioButton("Select", () => selectTool(null)))
.ToList(); .ToList();
toolboxCollection.Items[0].Select(); toolboxCollection.Items[0].Select();
blueprintContainer.SelectionChanged += selectionChanged;
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@ -130,14 +141,14 @@ namespace osu.Game.Rulesets.Edit
beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
editorBeatmap = new EditorBeatmap<TObject>(playableBeatmap); EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
editorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectAdded += addHitObject;
editorBeatmap.HitObjectRemoved += removeHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject;
editorBeatmap.StartTimeChanged += updateHitObject; EditorBeatmap.StartTimeChanged += updateHitObject;
var dependencies = new DependencyContainer(parent); var dependencies = new DependencyContainer(parent);
dependencies.CacheAs<IEditorBeatmap>(editorBeatmap); dependencies.CacheAs<IEditorBeatmap>(EditorBeatmap);
dependencies.CacheAs<IEditorBeatmap<TObject>>(editorBeatmap); dependencies.CacheAs<IEditorBeatmap<TObject>>(EditorBeatmap);
Config = dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset); Config = dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset);
@ -151,6 +162,14 @@ namespace osu.Game.Rulesets.Edit
inputManager = GetContainingInputManager(); inputManager = GetContainingInputManager();
} }
protected override void Update()
{
base.Update();
if (EditorClock.ElapsedFrameTime != 0 && blueprintContainer.CurrentTool != null)
showGridFor(Enumerable.Empty<HitObject>());
}
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
@ -164,19 +183,53 @@ namespace osu.Game.Rulesets.Edit
}); });
} }
private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); private void selectionChanged(IEnumerable<HitObject> selectedHitObjects)
private void removeHitObject(HitObject hitObject)
{ {
beatmapProcessor?.PreProcess(); var hitObjects = selectedHitObjects.ToArray();
beatmapProcessor?.PostProcess();
if (!hitObjects.Any())
distanceSnapGridContainer.Hide();
else
showGridFor(hitObjects);
} }
private void updateHitObject(HitObject hitObject) private void selectTool(HitObjectCompositionTool tool)
{ {
beatmapProcessor?.PreProcess(); blueprintContainer.CurrentTool = tool;
hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty);
beatmapProcessor?.PostProcess(); if (tool == null)
distanceSnapGridContainer.Hide();
else
showGridFor(Enumerable.Empty<HitObject>());
}
private void showGridFor(IEnumerable<HitObject> selectedHitObjects)
{
distanceSnapGridContainer.Clear();
distanceSnapGrid = CreateDistanceSnapGrid(selectedHitObjects);
if (distanceSnapGrid != null)
{
distanceSnapGridContainer.Child = distanceSnapGrid;
distanceSnapGridContainer.Show();
}
}
private ScheduledDelegate scheduledUpdate;
private void addHitObject(HitObject hitObject) => updateHitObject(hitObject);
private void removeHitObject(HitObject hitObject) => updateHitObject(null);
private void updateHitObject([CanBeNull] HitObject hitObject)
{
scheduledUpdate?.Cancel();
scheduledUpdate = Schedule(() =>
{
beatmapProcessor?.PreProcess();
hitObject?.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty);
beatmapProcessor?.PostProcess();
});
} }
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
@ -188,20 +241,30 @@ namespace osu.Game.Rulesets.Edit
public void BeginPlacement(HitObject hitObject) public void BeginPlacement(HitObject hitObject)
{ {
if (distanceSnapGrid != null)
hitObject.StartTime = GetSnappedTime(hitObject.StartTime, distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position));
} }
public void EndPlacement(HitObject hitObject) => editorBeatmap.Add(hitObject); public void EndPlacement(HitObject hitObject)
{
EditorBeatmap.Add(hitObject);
showGridFor(Enumerable.Empty<HitObject>());
}
public void Delete(HitObject hitObject) => editorBeatmap.Remove(hitObject); public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnapPosition(position) ?? position;
public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime;
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (editorBeatmap != null) if (EditorBeatmap != null)
{ {
editorBeatmap.HitObjectAdded -= addHitObject; EditorBeatmap.HitObjectAdded -= addHitObject;
editorBeatmap.HitObjectRemoved -= removeHitObject; EditorBeatmap.HitObjectRemoved -= removeHitObject;
} }
} }
} }
@ -234,5 +297,17 @@ namespace osu.Game.Rulesets.Edit
/// Creates a <see cref="SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections. /// Creates a <see cref="SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
/// </summary> /// </summary>
public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler();
/// <summary>
/// Creates the <see cref="DistanceSnapGrid"/> applicable for a <see cref="HitObject"/> selection.
/// </summary>
/// <param name="selectedHitObjects">The <see cref="HitObject"/> selection.</param>
/// <returns>The <see cref="DistanceSnapGrid"/> for <paramref name="selectedHitObjects"/>.</returns>
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
public abstract Vector2 GetSnappedPosition(Vector2 position);
public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition);
} }
} }

View File

@ -78,6 +78,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
public JudgementResult Result { get; private set; } public JudgementResult Result { get; private set; }
private Bindable<double> startTimeBindable;
private Bindable<int> comboIndexBindable; private Bindable<int> comboIndexBindable;
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
@ -127,7 +128,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
base.LoadComplete(); base.LoadComplete();
apply(HitObject); HitObject.DefaultsApplied += onDefaultsApplied;
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
startTimeBindable.BindValueChanged(_ => updateState(ArmedState.Idle, true));
if (HitObject is IHasComboInformation combo) if (HitObject is IHasComboInformation combo)
{ {
@ -136,8 +140,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
} }
updateState(ArmedState.Idle, true); updateState(ArmedState.Idle, true);
onDefaultsApplied();
} }
private void onDefaultsApplied() => apply(HitObject);
private void apply(HitObject hitObject) private void apply(HitObject hitObject)
{ {
#pragma warning disable 618 // can be removed 20200417 #pragma warning disable 618 // can be removed 20200417
@ -494,6 +501,12 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
/// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param> /// <param name="judgement">The <see cref="Judgement"/> that provides the scoring information.</param>
protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement); protected virtual JudgementResult CreateResult(Judgement judgement) => new JudgementResult(HitObject, judgement);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
HitObject.DefaultsApplied -= onDefaultsApplied;
}
} }
public abstract class DrawableHitObject<TObject> : DrawableHitObject public abstract class DrawableHitObject<TObject> : DrawableHitObject

View File

@ -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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -28,6 +29,11 @@ namespace osu.Game.Rulesets.Objects
/// </summary> /// </summary>
private const double control_point_leniency = 1; private const double control_point_leniency = 1;
/// <summary>
/// Invoked after <see cref="ApplyDefaults"/> has completed on this <see cref="HitObject"/>.
/// </summary>
public event Action DefaultsApplied;
public readonly Bindable<double> StartTimeBindable = new Bindable<double>(); public readonly Bindable<double> StartTimeBindable = new Bindable<double>();
/// <summary> /// <summary>
@ -113,6 +119,8 @@ namespace osu.Game.Rulesets.Objects
foreach (var h in nestedHitObjects) foreach (var h in nestedHitObjects)
h.ApplyDefaults(controlPointInfo, difficulty); h.ApplyDefaults(controlPointInfo, difficulty);
DefaultsApplied?.Invoke();
} }
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)

View File

@ -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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -14,20 +15,20 @@ 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
{ {
public class BlueprintContainer : CompositeDrawable public class BlueprintContainer : CompositeDrawable
{ {
private SelectionBlueprintContainer selectionBlueprints; public event Action<IEnumerable<HitObject>> SelectionChanged;
private SelectionBlueprintContainer selectionBlueprints;
private Container<PlacementBlueprint> placementBlueprintContainer; private Container<PlacementBlueprint> placementBlueprintContainer;
private PlacementBlueprint currentPlacement; private PlacementBlueprint currentPlacement;
private SelectionHandler selectionHandler; private SelectionHandler selectionHandler;
private InputManager inputManager; private InputManager inputManager;
private IEnumerable<SelectionBlueprint> selections => selectionBlueprints.Children.Where(c => c.IsAlive);
[Resolved] [Resolved]
private HitObjectComposer composer { get; set; } private HitObjectComposer composer { get; set; }
@ -143,7 +144,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
if (currentPlacement != null) if (currentPlacement != null)
{ {
currentPlacement.UpdatePosition(e.ScreenSpaceMousePosition); updatePlacementPosition(e.ScreenSpaceMousePosition);
return true; return true;
} }
@ -178,19 +179,27 @@ 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
blueprint.UpdatePosition(inputManager.CurrentState.Mouse.Position); updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
} }
} }
private void updatePlacementPosition(Vector2 screenSpacePosition)
{
Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition));
Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition);
currentPlacement.UpdatePosition(snappedScreenSpacePosition);
}
/// <summary> /// <summary>
/// Select all masks in a given rectangle selection area. /// Select all masks in a given rectangle selection area.
/// </summary> /// </summary>
/// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param> /// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param>
private void select(RectangleF rect) private void select(RectangleF rect)
{ {
foreach (var blueprint in selections.ToList()) foreach (var blueprint in selectionBlueprints)
{ {
if (blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint))
blueprint.Select(); blueprint.Select();
else else
blueprint.Deselect(); blueprint.Deselect();
@ -200,27 +209,40 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// Deselects all selected <see cref="SelectionBlueprint"/>s. /// Deselects all selected <see cref="SelectionBlueprint"/>s.
/// </summary> /// </summary>
private void deselectAll() => selections.ToList().ForEach(m => m.Deselect()); private void deselectAll() => selectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect());
private void onBlueprintSelected(SelectionBlueprint blueprint) private void onBlueprintSelected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleSelected(blueprint); selectionHandler.HandleSelected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 1); selectionBlueprints.ChangeChildDepth(blueprint, 1);
SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects);
} }
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleDeselected(blueprint); selectionHandler.HandleDeselected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 0); selectionBlueprints.ChangeChildDepth(blueprint, 0);
SelectionChanged?.Invoke(selectionHandler.SelectedHitObjects);
} }
private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state);
private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent)
{ {
var movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; HitObject draggedObject = blueprint.DrawableObject.HitObject;
selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, movePosition)); Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition;
Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition));
// Move the hitobjects
selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition)));
// Apply the start time at the newly snapped-to position
double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime;
foreach (HitObject obj in selectionHandler.SelectedHitObjects)
obj.StartTime += offset;
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -26,10 +26,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
public const float BORDER_RADIUS = 2; public const float BORDER_RADIUS = 2;
protected IEnumerable<SelectionBlueprint> SelectedBlueprints => selectedBlueprints; public IEnumerable<SelectionBlueprint> SelectedBlueprints => selectedBlueprints;
private readonly List<SelectionBlueprint> selectedBlueprints; private readonly List<SelectionBlueprint> selectedBlueprints;
protected IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject); public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject);
private Drawable outline; private Drawable outline;