1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 11:47:24 +08:00

Implement auto additions editor-only

This commit is contained in:
OliBomby 2024-08-29 21:54:47 +02:00 committed by Dean Herbert
parent 1b4215576d
commit 0a78eb9628
No known key found for this signature in database
6 changed files with 87 additions and 60 deletions

View File

@ -60,12 +60,18 @@ namespace osu.Game.Audio
/// </summary> /// </summary>
public int Volume { get; } public int Volume { get; }
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100) /// <summary>
/// Whether this sample should automatically assign the bank of the normal sample whenever it is set in the editor.
/// </summary>
public bool EditorAutoBank { get; }
public HitSampleInfo(string name, string bank = SampleControlPoint.DEFAULT_BANK, string? suffix = null, int volume = 100, bool editorAutoBank = true)
{ {
Name = name; Name = name;
Bank = bank; Bank = bank;
Suffix = suffix; Suffix = suffix;
Volume = volume; Volume = volume;
EditorAutoBank = editorAutoBank;
} }
/// <summary> /// <summary>
@ -92,9 +98,10 @@ namespace osu.Game.Audio
/// <param name="newBank">An optional new sample bank.</param> /// <param name="newBank">An optional new sample bank.</param>
/// <param name="newSuffix">An optional new lookup suffix.</param> /// <param name="newSuffix">An optional new lookup suffix.</param>
/// <param name="newVolume">An optional new volume.</param> /// <param name="newVolume">An optional new volume.</param>
/// <param name="newEditorAutoBank">An optional new editor auto bank flag.</param>
/// <returns>The new <see cref="HitSampleInfo"/>.</returns> /// <returns>The new <see cref="HitSampleInfo"/>.</returns>
public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default) public virtual HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
=> new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume)); => new HitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newSuffix.GetOr(Suffix), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank));
public virtual bool Equals(HitSampleInfo? other) public virtual bool Equals(HitSampleInfo? other)
=> other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix; => other != null && Name == other.Name && Bank == other.Bank && Suffix == other.Suffix;

View File

@ -539,7 +539,7 @@ namespace osu.Game.Beatmaps.Formats
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false) private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false)
{ {
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);
LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL)?.Bank); LegacySampleBank addBank = toLegacySampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name) && s.Name != HitSampleInfo.HIT_NORMAL && !s.EditorAutoBank)?.Bank);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();

View File

@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Objects
// Fall back to using the normal sample bank otherwise. // Fall back to using the normal sample bank otherwise.
if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal) if (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) is HitSampleInfo existingNormal)
return existingNormal.With(newName: sampleName); return existingNormal.With(newName: sampleName, newEditorAutoBank: true);
return new HitSampleInfo(sampleName); return new HitSampleInfo(sampleName);
} }

View File

@ -204,8 +204,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (stringBank == @"none") if (stringBank == @"none")
stringBank = null; stringBank = null;
string stringAddBank = addBank.ToString().ToLowerInvariant(); string stringAddBank = addBank.ToString().ToLowerInvariant();
if (stringAddBank == @"none") if (stringAddBank == @"none")
{
bankInfo.EditorAutoBank = true;
stringAddBank = null; stringAddBank = null;
}
else
bankInfo.EditorAutoBank = false;
bankInfo.BankForNormal = stringBank; bankInfo.BankForNormal = stringBank;
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
@ -477,7 +483,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (string.IsNullOrEmpty(bankInfo.Filename)) if (string.IsNullOrEmpty(bankInfo.Filename))
{ {
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank, soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, true, bankInfo.CustomSampleBank,
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal))); type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal)));
@ -489,13 +495,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
if (type.HasFlag(LegacyHitSoundType.Finish)) if (type.HasFlag(LegacyHitSoundType.Finish))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Whistle)) if (type.HasFlag(LegacyHitSoundType.Whistle))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
if (type.HasFlag(LegacyHitSoundType.Clap)) if (type.HasFlag(LegacyHitSoundType.Clap))
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank)); soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.EditorAutoBank, bankInfo.CustomSampleBank));
return soundTypes; return soundTypes;
} }
@ -534,6 +540,11 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// </summary> /// </summary>
public int CustomSampleBank; public int CustomSampleBank;
/// <summary>
/// Whether the bank for additions should be inherited from the normal sample in edit.
/// </summary>
public bool EditorAutoBank;
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
} }
@ -558,21 +569,21 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// </summary> /// </summary>
public bool BankSpecified; public bool BankSpecified;
public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, int customSampleBank = 0, bool isLayered = false) public LegacyHitSampleInfo(string name, string? bank = null, int volume = 0, bool editorAutoBank = false, int customSampleBank = 0, bool isLayered = false)
: base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume) : base(name, bank ?? SampleControlPoint.DEFAULT_BANK, customSampleBank >= 2 ? customSampleBank.ToString() : null, volume, editorAutoBank)
{ {
CustomSampleBank = customSampleBank; CustomSampleBank = customSampleBank;
BankSpecified = !string.IsNullOrEmpty(bank); BankSpecified = !string.IsNullOrEmpty(bank);
IsLayered = isLayered; IsLayered = isLayered;
} }
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default) public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default)
=> With(newName, newBank, newVolume); => With(newName, newBank, newVolume, newEditorAutoBank);
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
Optional<int> newCustomSampleBank = default, Optional<int> newCustomSampleBank = default,
Optional<bool> newIsLayered = default) Optional<bool> newIsLayered = default)
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered)); => new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newEditorAutoBank.GetOr(EditorAutoBank), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
public bool Equals(LegacyHitSampleInfo? other) public bool Equals(LegacyHitSampleInfo? other)
// The additions to equality checks here are *required* to ensure that pooling works correctly. // The additions to equality checks here are *required* to ensure that pooling works correctly.
@ -604,7 +615,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
Path.ChangeExtension(Filename, null) Path.ChangeExtension(Filename, null)
}; };
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<int> newVolume = default, Optional<bool> newEditorAutoBank = default,
Optional<int> newCustomSampleBank = default, Optional<int> newCustomSampleBank = default,
Optional<bool> newIsLayered = default) Optional<bool> newIsLayered = default)
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume)); => new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));

View File

@ -152,10 +152,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
else else
{ {
// Auto should never apply when there is a selection made.
if (bankName == HIT_BANK_AUTO)
break;
// Completely empty selections should be allowed in the case that none of the selected objects have any addition samples. // Completely empty selections should be allowed in the case that none of the selected objects have any addition samples.
// This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true). // This is also required to stop a bindable feedback loop when a HitObject has zero addition samples (and LINQ `All` below becomes true).
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
@ -163,8 +159,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Never remove a sample bank. // Never remove a sample bank.
// These are basically radio buttons, not toggles. // These are basically radio buttons, not toggles.
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))) if (bankName == HIT_BANK_AUTO)
bindable.Value = TernaryState.True; {
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank)))
bindable.Value = TernaryState.True;
}
else
{
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank)))
bindable.Value = TernaryState.True;
}
} }
break; break;
@ -182,14 +186,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
else else
{ {
// Auto should just not apply if there's a selection already made.
// Maybe we could make it a disabled button in the future, but right now the editor buttons don't support disabled state.
if (bankName == HIT_BANK_AUTO)
{
bindable.Value = TernaryState.False;
break;
}
// If none of the selected objects have any addition samples, we should not apply the addition bank. // If none of the selected objects have any addition samples, we should not apply the addition bank.
if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL))) if (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
{ {
@ -209,7 +205,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// start with normal selected. // start with normal selected.
SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; SelectionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True;
SelectionAdditionBankStates[SampleControlPoint.DEFAULT_BANK].Value = TernaryState.True; SelectionAdditionBankStates[HIT_BANK_AUTO].Value = TernaryState.True;
foreach (string sampleName in HitSampleInfo.AllAdditions) foreach (string sampleName in HitSampleInfo.AllAdditions)
{ {
@ -272,7 +268,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach ((string bankName, var bindable) in SelectionAdditionBankStates) foreach ((string bankName, var bindable) in SelectionAdditionBankStates)
{ {
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName); bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name != HitSampleInfo.HIT_NORMAL), h => (bankName != HIT_BANK_AUTO && h.Bank == bankName && !h.EditorAutoBank) || (bankName == HIT_BANK_AUTO && h.EditorAutoBank));
} }
} }
@ -336,33 +332,29 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <param name="bankName">The name of the sample bank.</param> /// <param name="bankName">The name of the sample bank.</param>
public void SetSampleAdditionBank(string bankName) public void SetSampleAdditionBank(string bankName)
{ {
bool hasRelevantBank(HitObject hitObject) bool hasRelevantBank(HitObject hitObject) =>
{ bankName == HIT_BANK_AUTO
bool result = hitObject.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName); ? enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.EditorAutoBank)
: enumerateAllSamples(hitObject).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName && !s.EditorAutoBank);
if (hitObject is IHasRepeats hasRepeats)
{
foreach (var node in hasRepeats.NodeSamples)
result &= node.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
}
return result;
}
if (SelectedItems.All(hasRelevantBank)) if (SelectedItems.All(hasRelevantBank))
return; return;
EditorBeatmap.PerformOnSelection(h => EditorBeatmap.PerformOnSelection(h =>
{ {
if (enumerateAllSamples(h).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)) if (hasRelevantBank(h))
return; return;
h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); string normalBank = h.Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList();
if (h is IHasRepeats hasRepeats) if (h is IHasRepeats hasRepeats)
{ {
for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i) for (int i = 0; i < hasRepeats.NodeSamples.Count; ++i)
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList(); {
normalBank = hasRepeats.NodeSamples[i].FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? bankName == HIT_BANK_AUTO ? s.With(newBank: normalBank, newEditorAutoBank: true) : s.With(newBank: bankName, newEditorAutoBank: false) : s).ToList();
}
} }
EditorBeatmap.Update(h); EditorBeatmap.Update(h);
@ -406,9 +398,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
var hitSample = h.CreateHitSampleInfo(sampleName); var hitSample = h.CreateHitSampleInfo(sampleName);
string? existingAdditionBank = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL)?.Bank; HitSampleInfo? existingAddition = node.FirstOrDefault(s => s.Name != HitSampleInfo.HIT_NORMAL);
if (existingAdditionBank != null) if (existingAddition != null)
hitSample = hitSample.With(newBank: existingAdditionBank); hitSample = hitSample.With(newBank: existingAddition.Bank, newEditorAutoBank: existingAddition.EditorAutoBank);
node.Add(hitSample); node.Add(hitSample);
} }

View File

@ -107,7 +107,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples) public static string? GetAdditionBankValue(IEnumerable<HitSampleInfo> samples)
{ {
return samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL)?.Bank; var firstAddition = samples.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL);
if (firstAddition == null)
return null;
return firstAddition.EditorAutoBank ? EditorSelectionHandler.HIT_BANK_AUTO : firstAddition.Bank;
} }
public static int GetVolumeValue(ICollection<HitSampleInfo> samples) public static int GetVolumeValue(ICollection<HitSampleInfo> samples)
@ -320,7 +324,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
for (int i = 0; i < relevantSamples.Count; i++) for (int i = 0; i < relevantSamples.Count; i++)
{ {
if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL) continue; if (relevantSamples[i].Name != HitSampleInfo.HIT_NORMAL && !relevantSamples[i].EditorAutoBank) continue;
relevantSamples[i] = relevantSamples[i].With(newBank: newBank); relevantSamples[i] = relevantSamples[i].With(newBank: newBank);
} }
@ -331,11 +335,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
updateAllRelevantSamples((_, relevantSamples) => updateAllRelevantSamples((_, relevantSamples) =>
{ {
string normalBank = relevantSamples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank ?? HitSampleInfo.BANK_SOFT;
for (int i = 0; i < relevantSamples.Count; i++) for (int i = 0; i < relevantSamples.Count; i++)
{ {
if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL) continue; if (relevantSamples[i].Name == HitSampleInfo.HIT_NORMAL)
continue;
relevantSamples[i] = relevantSamples[i].With(newBank: newBank); // Addition samples with bank set to auto should inherit the bank of the normal sample
if (newBank == EditorSelectionHandler.HIT_BANK_AUTO)
{
relevantSamples[i] = relevantSamples[i].With(newBank: normalBank, newEditorAutoBank: true);
}
else
relevantSamples[i] = relevantSamples[i].With(newBank: newBank, newEditorAutoBank: false);
} }
}); });
} }
@ -383,7 +396,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
selectionSampleStates[sampleName] = bindable; selectionSampleStates[sampleName] = bindable;
} }
banks.AddRange(HitSampleInfo.AllBanks); banks.AddRange(HitSampleInfo.AllBanks.Prepend(EditorSelectionHandler.HIT_BANK_AUTO));
} }
private void updateTernaryStates() private void updateTernaryStates()
@ -448,7 +461,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (string.IsNullOrEmpty(newBank)) if (string.IsNullOrEmpty(newBank))
return true; return true;
if (e.ShiftPressed) if (e.ShiftPressed && newBank != EditorSelectionHandler.HIT_BANK_AUTO)
{ {
setBank(newBank); setBank(newBank);
updatePrimaryBankState(); updatePrimaryBankState();
@ -462,7 +475,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
else else
{ {
var item = togglesCollection.ElementAtOrDefault(rightIndex); var item = togglesCollection.ElementAtOrDefault(rightIndex - 1);
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e); if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
@ -476,18 +489,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
switch (key) switch (key)
{ {
case Key.W: case Key.Q:
index = 0; index = 0;
break; break;
case Key.E: case Key.W:
index = 1; index = 1;
break; break;
case Key.R: case Key.E:
index = 2; index = 2;
break; break;
case Key.R:
index = 3;
break;
default: default:
index = -1; index = -1;
break; break;