diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs index 5b2b6fc780..f30c56d53c 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSamplePointAdjustments.cs @@ -171,15 +171,15 @@ namespace osu.Game.Tests.Visual.Editing private void samplePopoverHasSingleVolume(int volume) => AddUntilStep($"sample popover has volume {volume}", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider?.Current.Value == volume; }); - private void samplePopoverHasIndeterminateVolume() => AddUntilStep($"sample popover has indeterminate volume", () => + private void samplePopoverHasIndeterminateVolume() => AddUntilStep("sample popover has indeterminate volume", () => { var popover = this.ChildrenOfType().SingleOrDefault(); - var slider = popover?.ChildrenOfType>().Single(); + var slider = popover?.ChildrenOfType>().Single(); return slider != null && slider.Current.Value == null; }); @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Editing var popover = this.ChildrenOfType().SingleOrDefault(); var textBox = popover?.ChildrenOfType().First(); - return textBox != null && textBox.Current.Value == null; + return textBox != null && string.IsNullOrEmpty(textBox.Current.Value); }); private void dismissPopover() @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Editing private void setVolumeViaPopover(int volume) => AddStep($"set volume {volume} via popover", () => { var popover = this.ChildrenOfType().Single(); - var slider = popover.ChildrenOfType>().Single(); + var slider = popover.ChildrenOfType>().Single(); slider.Current.Value = volume; }); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 6250a9ccb8..f0b11ce96e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -1,9 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -55,18 +60,16 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class SampleEditPopover : OsuPopover { private readonly HitObject hitObject; - private readonly SampleControlPoint point; - private LabelledTextBox bank; - private SliderWithTextBoxInput volume; + private LabelledTextBox bank = null!; + private IndeterminateSliderWithTextBoxInput volume = null!; [Resolved(canBeNull: true)] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; public SampleEditPopover(HitObject hitObject) { this.hitObject = hitObject; - point = hitObject.SampleControlPoint; } [BackgroundDependencyLoader] @@ -85,19 +88,61 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Label = "Bank Name", }, - volume = new SliderWithTextBoxInput("Volume") - { - Current = new SampleControlPoint().SampleVolumeBindable, - } + volume = new IndeterminateSliderWithTextBoxInput("Volume", new SampleControlPoint().SampleVolumeBindable) } } }; - bank.Current = point.SampleBankBindable; - bank.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // 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. + var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); + var relevantControlPoints = relevantObjects.Select(h => h.SampleControlPoint).ToArray(); - volume.Current = point.SampleVolumeBindable; - volume.Current.BindValueChanged(_ => beatmap.Update(hitObject)); + // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. + string? commonBank = relevantControlPoints.Select(point => point.SampleBank).Distinct().Count() == 1 ? relevantControlPoints.First().SampleBank : null; + + if (!string.IsNullOrEmpty(commonBank)) + bank.Current.Value = commonBank; + + int? commonVolume = relevantControlPoints.Select(point => point.SampleVolume).Distinct().Count() == 1 ? (int?)relevantControlPoints.First().SampleVolume : null; + + if (commonVolume != null) + volume.Current.Value = commonVolume.Value; + + bank.Current.BindValueChanged(val => updateBankFor(relevantObjects, val.NewValue)); + volume.Current.BindValueChanged(val => updateVolumeFor(relevantObjects, val.NewValue)); + } + + private void updateBankFor(IEnumerable objects, string? newBank) + { + if (string.IsNullOrEmpty(newBank)) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleBank = newBank; + beatmap.Update(h); + } + + beatmap.EndChange(); + } + + private void updateVolumeFor(IEnumerable objects, int? newVolume) + { + if (newVolume == null) + return; + + beatmap.BeginChange(); + + foreach (var h in objects) + { + h.SampleControlPoint.SampleVolume = newVolume.Value; + beatmap.Update(h); + } + + beatmap.EndChange(); } } }