mirror of
https://github.com/ppy/osu.git
synced 2025-01-19 04:22:55 +08:00
Merge pull request #29039 from bdach/multiple-selection-sample-popover
Make sample popover change properties of all samples in multiple selection
This commit is contained in:
commit
4983e5f33e
@ -402,6 +402,70 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
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<IList<HitSampleInfo>>
|
||||
{
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
|
||||
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
|
||||
},
|
||||
new List<HitSampleInfo>
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
@ -34,12 +34,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
private readonly int nodeIndex;
|
||||
|
||||
protected override IList<HitSampleInfo> GetRelevantSamples(HitObject ho)
|
||||
protected override IEnumerable<(HitObject hitObject, IList<HitSampleInfo> 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)
|
||||
|
@ -21,6 +21,7 @@ using osu.Game.Graphics.UserInterfaceV2;
|
||||
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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private FillFlowContainer togglesCollection = null!;
|
||||
|
||||
private HitObject[] relevantObjects = null!;
|
||||
private IList<HitSampleInfo>[] allRelevantSamples = null!;
|
||||
private (HitObject hitObject, IList<HitSampleInfo> samples)[] allRelevantSamples = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="ho">The hit object to get the relevant samples from.</param>
|
||||
/// <param name="hitObjects">The hit objects to get the relevant samples from.</param>
|
||||
/// <returns>The relevant list of samples.</returns>
|
||||
protected virtual IList<HitSampleInfo> GetRelevantSamples(HitObject ho) => ho.Samples;
|
||||
protected virtual IEnumerable<(HitObject hitObject, IList<HitSampleInfo> 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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
// 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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
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<string>().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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
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 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
foreach ((string sampleName, var bindable) in selectionSampleStates)
|
||||
{
|
||||
bindable.Value = SelectionHandler<HitObject>.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName));
|
||||
bindable.Value = SelectionHandler<HitObject>.GetStateFromSelection(GetRelevantSamples(relevantObjects), h => h.samples.Any(s => s.Name == sampleName));
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user