diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index f02d2a1bb1bd..9988c1cb59a9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -402,6 +402,88 @@ public void TestHotkeysDuringPlacement() void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); } + [Test] + public void TestHotkeysAffectNodeSamples() + { + 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)); + + AddStep("add clap addition", () => InputManager.Key(Key.R)); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); + + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); + + hitObjectHasSampleBank(2, HitSampleInfo.BANK_NORMAL); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); + hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); + hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE, HitSampleInfo.HIT_CLAP); + + AddStep("remove clap addition", () => InputManager.Key(Key.R)); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(2, HitSampleInfo.BANK_NORMAL); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + AddStep("set drum bank", () => + { + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LShift); + }); + + hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL); + + hitObjectHasSampleBank(2, HitSampleInfo.BANK_DRUM); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL); + hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => { var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 7c30b731220d..70c91b16fd3f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -117,7 +117,7 @@ private void createStateBindables() break; } - AddSampleBank(bankName); + SetSampleBank(bankName); } break; @@ -177,14 +177,27 @@ protected virtual void UpdateTernaryStates() { SelectionNewComboState.Value = GetStateFromSelection(SelectedItems.OfType(), h => h.NewCombo); + var samplesInSelection = SelectedItems.SelectMany(enumerateAllSamples).ToArray(); + foreach ((string sampleName, var bindable) in SelectionSampleStates) { - bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.Any(s => s.Name == sampleName)); + bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Name == sampleName)); } foreach ((string bankName, var bindable) in SelectionBankStates) { - bindable.Value = GetStateFromSelection(SelectedItems, h => h.Samples.All(s => s.Bank == bankName)); + bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Bank == bankName)); + } + + IEnumerable> enumerateAllSamples(HitObject hitObject) + { + yield return hitObject.Samples; + + if (hitObject is IHasRepeats withRepeats) + { + foreach (var node in withRepeats.NodeSamples) + yield return node; + } } } @@ -193,12 +206,25 @@ protected virtual void UpdateTernaryStates() #region Ternary state changes /// - /// Adds a sample bank to all selected s. + /// Sets the sample bank for all selected s. /// /// The name of the sample bank. - public void AddSampleBank(string bankName) + public void SetSampleBank(string bankName) { - if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) + bool hasRelevantBank(HitObject hitObject) + { + bool result = hitObject.Samples.All(s => s.Bank == bankName); + + if (hitObject is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + result &= node.All(s => s.Bank == bankName); + } + + return result; + } + + if (SelectedItems.All(hasRelevantBank)) return; EditorBeatmap.PerformOnSelection(h => @@ -207,17 +233,37 @@ public void AddSampleBank(string bankName) return; h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); + + if (h is IHasRepeats hasRepeats) + { + for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.With(newBank: bankName)).ToList(); + } + EditorBeatmap.Update(h); }); } + private bool hasRelevantSample(HitObject hitObject, string sampleName) + { + bool result = hitObject.Samples.Any(s => s.Name == sampleName); + + if (hitObject is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + result &= node.Any(s => s.Name == sampleName); + } + + return result; + } + /// /// Adds a hit sample to all selected s. /// /// The name of the hit sample. public void AddHitSample(string sampleName) { - if (SelectedItems.All(h => h.Samples.Any(s => s.Name == sampleName))) + if (SelectedItems.All(h => hasRelevantSample(h, sampleName))) return; EditorBeatmap.PerformOnSelection(h => @@ -228,6 +274,23 @@ public void AddHitSample(string sampleName) h.Samples.Add(h.CreateHitSampleInfo(sampleName)); + if (h is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + { + if (node.Any(s => s.Name == sampleName)) + continue; + + var hitSample = h.CreateHitSampleInfo(sampleName); + + string? existingAdditionBank = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL)?.Bank; + if (existingAdditionBank != null) + hitSample = hitSample.With(newBank: existingAdditionBank); + + node.Add(hitSample); + } + } + EditorBeatmap.Update(h); }); } @@ -238,12 +301,19 @@ public void AddHitSample(string sampleName) /// The name of the hit sample. public void RemoveHitSample(string sampleName) { - if (SelectedItems.All(h => h.Samples.All(s => s.Name != sampleName))) + if (SelectedItems.All(h => !hasRelevantSample(h, sampleName))) return; EditorBeatmap.PerformOnSelection(h => { h.SamplesBindable.RemoveAll(s => s.Name == sampleName); + + if (h is IHasRepeats hasRepeats) + { + for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) + hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Where(s => s.Name != sampleName).ToList(); + } + EditorBeatmap.Update(h); }); }