Ensure selection is preserved when moving selection between columns

Closes https://github.com/ppy/osu/issues/29793.

I believe that the sequence of events that makes this happens is as
follows:

- User selects a range of objects. Some of those objects are off-screen,
  and thus would be presumed to be not alive - except the blueprint
  container forces them to remain alive, because they're part of the
  selection.

- User moves the selection to another column, which is implemented by
  temporarily removing the objects from the playfield, changing their
  column, and re-adding them.

  This sort of pattern is supposed to kick off the
  `HitObjectUsageTransferred` flow in `HitObjectUsageEventBuffer` - and
  it does... for objects that are *currently visible on screen* and thus
  would be alive regardless of `SetKeepAlive()`. However, this does not
  hold for objects that are off-screen - nothing ensures they are kept
  alive again after re-adding, and thus they inadvertently become dead.

- Thus, this doesn't kick off the `BlueprintContainer` flows associated
  with transferring objects to another column, and instead fires the
  removal flows, which ensure that the off-screen objects that were
  being moved are instead deselected.

I tried a few other options but found no better resolution than this -
calling `SetKeepAlive()` directly would require making it public, which
seems like a bad idea. There's really no good way to generically handle
this either, because it is the ruleset that decides that its way of
implementing this operation will be a removal and re-add of objects,
so...
This commit is contained in:
Bartłomiej Dach 2024-09-17 13:07:57 +02:00
parent e0f92bab6a
commit 20b1d76269
No known key found for this signature in database
1 changed files with 10 additions and 1 deletions

View File

@ -104,8 +104,10 @@ private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject>
int minColumn = int.MaxValue;
int maxColumn = int.MinValue;
var selectedObjects = EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>().ToArray();
// find min/max in an initial pass before actually performing the movement.
foreach (var obj in EditorBeatmap.SelectedHitObjects.OfType<ManiaHitObject>())
foreach (var obj in selectedObjects)
{
if (obj.Column < minColumn)
minColumn = obj.Column;
@ -121,6 +123,13 @@ private void performColumnMovement(int lastColumn, MoveSelectionEvent<HitObject>
((ManiaHitObject)h).Column += columnDelta;
maniaPlayfield.Add(h);
});
// `HitObjectUsageEventBuffer`'s usage transferal flows and the playfield's `SetKeepAlive()` functionality do not combine well with this operation's usage pattern,
// leading to selections being sometimes partially dropped if some of the objects being moved are off screen
// (check blame for detailed explanation).
// thus, ensure that selection is preserved manually.
EditorBeatmap.SelectedHitObjects.Clear();
EditorBeatmap.SelectedHitObjects.AddRange(selectedObjects);
}
}
}