mirror of
https://github.com/ppy/osu.git
synced 2025-03-12 02:27:28 +08:00
Closes https://github.com/ppy/osu/issues/28791. The reason why nudging was not changing hyperdash state in catch was that `EditorBeatmap.Update()` was not being called on the objects that were being modified, therefore postprocessing was not performed, therefore hyperdash state was not being recomputed. Looking at the usage sites of `EditorBeatmap.PerformOnSelection()`, about two-thirds of callers called `Update()` themselves on the objects they mutated, and the rest didn't. I'd say that's the failure of the abstraction and it should be `PerformOnSelection()`'s responsibility to call `Update()` there. Yes in some of the cases here this will cause extraneous calls that weren't done before, but the method is already heavily disclaimed as 'expensive', so I'd say usability should come first.
180 lines
6.0 KiB
C#
180 lines
6.0 KiB
C#
// 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.
|
|
|
|
#nullable disable
|
|
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Input;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Game.Rulesets.Edit;
|
|
using osu.Game.Rulesets.Objects;
|
|
using osu.Game.Rulesets.Objects.Drawables;
|
|
|
|
namespace osu.Game.Screens.Edit.Compose.Components
|
|
{
|
|
public abstract partial class EditorBlueprintContainer : BlueprintContainer<HitObject>
|
|
{
|
|
[Resolved]
|
|
protected EditorClock EditorClock { get; private set; }
|
|
|
|
[Resolved]
|
|
protected EditorBeatmap Beatmap { get; private set; }
|
|
|
|
protected readonly HitObjectComposer Composer;
|
|
|
|
private HitObjectUsageEventBuffer usageEventBuffer;
|
|
|
|
protected InputManager InputManager { get; private set; }
|
|
|
|
protected EditorBlueprintContainer(HitObjectComposer composer)
|
|
{
|
|
Composer = composer;
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
SelectedItems.BindTo(Beatmap.SelectedHitObjects);
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
|
|
InputManager = GetContainingInputManager();
|
|
|
|
Beatmap.HitObjectAdded += AddBlueprintFor;
|
|
Beatmap.HitObjectRemoved += RemoveBlueprintFor;
|
|
Beatmap.SelectedHitObjects.CollectionChanged += updateSelectionLifetime;
|
|
|
|
if (Composer != null)
|
|
{
|
|
foreach (var obj in Composer.HitObjects)
|
|
AddBlueprintFor(obj.HitObject);
|
|
|
|
usageEventBuffer = new HitObjectUsageEventBuffer(Composer.Playfield);
|
|
usageEventBuffer.HitObjectUsageBegan += AddBlueprintFor;
|
|
usageEventBuffer.HitObjectUsageFinished += RemoveBlueprintFor;
|
|
usageEventBuffer.HitObjectUsageTransferred += TransferBlueprintFor;
|
|
}
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
usageEventBuffer?.Update();
|
|
}
|
|
|
|
protected override IEnumerable<SelectionBlueprint<HitObject>> SortForMovement(IReadOnlyList<SelectionBlueprint<HitObject>> blueprints)
|
|
=> blueprints.OrderBy(b => b.Item.StartTime);
|
|
|
|
protected void ApplySnapResultTime(SnapResult result, double referenceTime)
|
|
{
|
|
if (!result.Time.HasValue)
|
|
return;
|
|
|
|
// Apply the start time at the newly snapped-to position
|
|
double offset = result.Time.Value - referenceTime;
|
|
|
|
if (offset != 0)
|
|
Beatmap.PerformOnSelection(obj => obj.StartTime += offset);
|
|
}
|
|
|
|
protected override void AddBlueprintFor(HitObject item)
|
|
{
|
|
if (item is IBarLine)
|
|
return;
|
|
|
|
base.AddBlueprintFor(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
|
|
/// </summary>
|
|
/// <param name="hitObject">The hit object which has been assigned to a new drawable.</param>
|
|
/// <param name="drawableObject">The new drawable that is representing the hit object.</param>
|
|
protected virtual void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
|
{
|
|
}
|
|
|
|
protected override void DragOperationCompleted()
|
|
{
|
|
base.DragOperationCompleted();
|
|
|
|
// handle positional change etc.
|
|
foreach (var blueprint in SelectionBlueprints)
|
|
Beatmap.Update(blueprint.Item);
|
|
}
|
|
|
|
protected override bool OnDoubleClick(DoubleClickEvent e)
|
|
{
|
|
if (!base.OnDoubleClick(e))
|
|
return false;
|
|
|
|
EditorClock?.SeekSmoothlyTo(ClickedBlueprint.Item.StartTime);
|
|
return true;
|
|
}
|
|
|
|
protected override SelectionBlueprintContainer CreateSelectionBlueprintContainer() => new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both };
|
|
|
|
protected override SelectionHandler<HitObject> CreateSelectionHandler() => new EditorSelectionHandler();
|
|
|
|
protected override void SelectAll()
|
|
{
|
|
Composer.Playfield.KeepAllAlive();
|
|
SelectedItems.AddRange(Beatmap.HitObjects.Except(SelectedItems).ToArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that newly-selected hitobjects are kept alive
|
|
/// and drops that keep-alive from newly-deselected objects.
|
|
/// </summary>
|
|
private void updateSelectionLifetime(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (HitObject newSelection in e.NewItems)
|
|
Composer.Playfield.SetKeepAlive(newSelection, true);
|
|
}
|
|
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (HitObject oldSelection in e.OldItems)
|
|
Composer.Playfield.SetKeepAlive(oldSelection, false);
|
|
}
|
|
}
|
|
|
|
protected override void OnBlueprintSelected(SelectionBlueprint<HitObject> blueprint)
|
|
{
|
|
base.OnBlueprintSelected(blueprint);
|
|
|
|
Composer.Playfield.SetKeepAlive(blueprint.Item, true);
|
|
}
|
|
|
|
protected override void OnBlueprintDeselected(SelectionBlueprint<HitObject> blueprint)
|
|
{
|
|
base.OnBlueprintDeselected(blueprint);
|
|
|
|
Composer.Playfield.SetKeepAlive(blueprint.Item, false);
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
if (Beatmap != null)
|
|
{
|
|
Beatmap.HitObjectAdded -= AddBlueprintFor;
|
|
Beatmap.HitObjectRemoved -= RemoveBlueprintFor;
|
|
Beatmap.SelectedHitObjects.CollectionChanged -= updateSelectionLifetime;
|
|
}
|
|
|
|
usageEventBuffer?.Dispose();
|
|
}
|
|
}
|
|
}
|