From 55382a4ba6edea3f1d26227bd125f8db7f3c8c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 12:08:12 +0200 Subject: [PATCH 1/2] Add test coverage for expected sample popover behaviour --- .../TestSceneHitObjectSampleAdjustments.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 558d8dce9488..75a68237c87d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -402,6 +402,70 @@ public void TestHotkeysDuringPlacement() void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); } + [Test] + public void PopoverForMultipleSelectionChangesAllSamples() + { + AddStep("add slider", () => + { + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 1000, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = new List> + { + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM), + new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM), + }, + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT), + }, + } + }); + }); + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + + clickSamplePiece(0); + + setBankViaPopover(HitSampleInfo.BANK_DRUM); + samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(2, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM); + + setVolumeViaPopover(30); + samplePopoverHasSingleVolume(30); + hitObjectHasSampleVolume(0, 30); + hitObjectHasSampleVolume(1, 30); + hitObjectHasSampleVolume(2, 30); + hitObjectNodeHasSampleVolume(2, 0, 30); + hitObjectNodeHasSampleVolume(2, 1, 30); + + toggleAdditionViaPopover(0); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(2, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT); + } + [Test] public void TestHotkeysAffectNodeSamples() { From 1ed7e4b075bafe9ccf2e33ef252e272c61bb7d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 14:34:48 +0200 Subject: [PATCH 2/2] Make sample popover change properties of all samples in multiple selection Closes https://github.com/ppy/osu/issues/28916. The previous behaviour *may* have been intended, but it was honestly quite baffling. This seems like a saner variant. --- .../Timeline/NodeSamplePointPiece.cs | 8 +-- .../Components/Timeline/SamplePointPiece.cs | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index ae3838bc413e..9cc1268db7ab 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -34,12 +34,12 @@ public partial class NodeSampleEditPopover : SampleEditPopover { private readonly int nodeIndex; - protected override IList GetRelevantSamples(HitObject ho) + protected override IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) { - if (ho is not IHasRepeats hasRepeats) - return ho.Samples; + if (hitObjects.Length > 1 || hitObjects[0] is not IHasRepeats hasRepeats) + return base.GetRelevantSamples(hitObjects); - return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; + return [(hitObjects[0], nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : hitObjects[0].Samples)]; } public NodeSampleEditPopover(HitObject hitObject, int nodeIndex) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 8c7603021a0b..8bfb0a33587d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; @@ -106,15 +107,34 @@ public partial class SampleEditPopover : OsuPopover private FillFlowContainer togglesCollection = null!; private HitObject[] relevantObjects = null!; - private IList[] allRelevantSamples = null!; + private (HitObject hitObject, IList samples)[] allRelevantSamples = null!; /// /// Gets the sub-set of samples relevant to this sample point piece. /// For example, to edit node samples this should return the samples at the index of the node. /// - /// The hit object to get the relevant samples from. + /// The hit objects to get the relevant samples from. /// The relevant list of samples. - protected virtual IList GetRelevantSamples(HitObject ho) => ho.Samples; + protected virtual IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) + { + if (hitObjects.Length == 1) + { + yield return (hitObjects[0], hitObjects[0].Samples); + + yield break; + } + + foreach (var ho in hitObjects) + { + yield return (ho, ho.Samples); + + if (ho is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + yield return (ho, node); + } + } + } [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; @@ -172,7 +192,7 @@ private void load() // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); + allRelevantSamples = GetRelevantSamples(relevantObjects).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. int? commonVolume = getCommonVolume(); @@ -214,9 +234,19 @@ private void load() togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } - private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; - private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Where(o => o is not null).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; - private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; + private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1 + ? GetBankValue(allRelevantSamples.First().samples) + : null; + + private string? getCommonAdditionBank() + { + string[] additionBanks = allRelevantSamples.Select(h => GetAdditionBankValue(h.samples)).Where(o => o is not null).Cast().Distinct().ToArray(); + return additionBanks.Length == 1 ? additionBanks[0] : null; + } + + private int? getCommonVolume() => allRelevantSamples.Select(h => GetVolumeValue(h.samples)).Distinct().Count() == 1 + ? GetVolumeValue(allRelevantSamples.First().samples) + : null; private void updatePrimaryBankState() { @@ -231,7 +261,7 @@ private void updateAdditionBankState() additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; additionBank.Current.Value = commonAdditionBank; - bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); if (anyAdditions) additionBank.Show(); else @@ -247,9 +277,8 @@ private void updateAllRelevantSamples(Action> up { beatmap.BeginChange(); - foreach (var relevantHitObject in relevantObjects) + foreach (var (relevantHitObject, relevantSamples) in GetRelevantSamples(relevantObjects)) { - var relevantSamples = GetRelevantSamples(relevantHitObject); updateAction(relevantHitObject, relevantSamples); beatmap.Update(relevantHitObject); } @@ -333,7 +362,7 @@ private void updateTernaryStates() { foreach ((string sampleName, var bindable) in selectionSampleStates) { - bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName)); + bindable.Value = SelectionHandler.GetStateFromSelection(GetRelevantSamples(relevantObjects), h => h.samples.Any(s => s.Name == sampleName)); } }