1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-21 14:52:56 +08:00

Merge pull request #28863 from OliBomby/additions

Add toggles and hotkeys for configuring sample addition bank
This commit is contained in:
Dean Herbert 2024-10-23 15:52:55 +09:00 committed by GitHub
commit 2103b3e186
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 541 additions and 98 deletions

View File

@ -362,6 +362,12 @@ namespace osu.Game.Tests.Visual.Editing
} }
}); });
AddStep("add whistle addition", () =>
{
foreach (var h in EditorBeatmap.HitObjects)
h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT));
});
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT);
@ -374,8 +380,10 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft); InputManager.ReleaseKey(Key.ShiftLeft);
}); });
hitObjectHasSampleBank(0, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_NORMAL); hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
AddStep("Press drum bank shortcut", () => AddStep("Press drum bank shortcut", () =>
{ {
@ -384,8 +392,10 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft); InputManager.ReleaseKey(Key.ShiftLeft);
}); });
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
AddStep("Press auto bank shortcut", () => AddStep("Press auto bank shortcut", () =>
{ {
@ -395,8 +405,47 @@ namespace osu.Game.Tests.Visual.Editing
}); });
// Should be a noop. // Should be a noop.
hitObjectHasSampleBank(0, HitSampleInfo.BANK_DRUM); hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM); hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT);
AddStep("Press addition normal bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL);
AddStep("Press addition drum bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
AddStep("Press auto bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
// Should be a noop.
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_DRUM);
} }
[Test] [Test]
@ -414,7 +463,21 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft); InputManager.ReleaseKey(Key.ShiftLeft);
}); });
checkPlacementSample(HitSampleInfo.BANK_NORMAL); AddStep("Press soft addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.E);
InputManager.ReleaseKey(Key.AltLeft);
});
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
AddStep("Press drum bank shortcut", () => AddStep("Press drum bank shortcut", () =>
{ {
@ -423,7 +486,18 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft); InputManager.ReleaseKey(Key.ShiftLeft);
}); });
checkPlacementSample(HitSampleInfo.BANK_DRUM); checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
AddStep("Press drum addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.AltLeft);
});
checkPlacementSampleBank(HitSampleInfo.BANK_DRUM);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
AddStep("Press auto bank shortcut", () => AddStep("Press auto bank shortcut", () =>
{ {
@ -432,15 +506,29 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.ShiftLeft); InputManager.ReleaseKey(Key.ShiftLeft);
}); });
checkPlacementSample(HitSampleInfo.BANK_NORMAL); checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_DRUM);
AddStep("Press auto addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
AddStep("Move after second object", () => EditorClock.Seek(750)); AddStep("Move after second object", () => EditorClock.Seek(750));
checkPlacementSample(HitSampleInfo.BANK_SOFT); checkPlacementSampleBank(HitSampleInfo.BANK_SOFT);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_SOFT);
AddStep("Move to first object", () => EditorClock.Seek(0)); AddStep("Move to first object", () => EditorClock.Seek(0));
checkPlacementSample(HitSampleInfo.BANK_NORMAL); checkPlacementSampleBank(HitSampleInfo.BANK_NORMAL);
checkPlacementSampleAdditionBank(HitSampleInfo.BANK_NORMAL);
void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); void checkPlacementSampleBank(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
void checkPlacementSampleAdditionBank(string expected) => AddAssert($"Placement sample addition is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
} }
[Test] [Test]
@ -585,7 +673,29 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL); hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL);
hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM); hitObjectNodeHasSampleBank(2, 0, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL); hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL);
hitObjectNodeHasSampleBank(2, 1, HitSampleInfo.BANK_DRUM); hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
AddStep("set normal addition bank", () =>
{
InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.LAlt);
});
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);
hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_NORMAL);
hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
} }
@ -629,20 +739,37 @@ namespace osu.Game.Tests.Visual.Editing
InputManager.ReleaseKey(Key.LShift); InputManager.ReleaseKey(Key.LShift);
}); });
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP);
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
AddStep("unify whistle addition", () => InputManager.Key(Key.W)); AddStep("unify whistle addition", () => InputManager.Key(Key.W));
hitObjectHasSampleBank(0, HitSampleInfo.BANK_SOFT); hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 0, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleBank(0, 1, HitSampleInfo.BANK_SOFT); hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
AddStep("set drum addition bank", () =>
{
InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.LAlt);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleNormalBank(0, 0, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSampleAdditionBank(0, 0, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSamples(0, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE);
hitObjectNodeHasSampleNormalBank(0, 1, HitSampleInfo.BANK_SOFT);
hitObjectNodeHasSampleAdditionBank(0, 1, HitSampleInfo.BANK_DRUM);
hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); hitObjectNodeHasSamples(0, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE);
} }

View File

@ -165,7 +165,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("enable automatic bank assignment", () => AddStep("enable automatic bank assignment", () =>
{ {
InputManager.PressKey(Key.LShift); InputManager.PressKey(Key.LShift);
InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.Q); InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.LAlt);
InputManager.ReleaseKey(Key.LShift); InputManager.ReleaseKey(Key.LShift);
}); });
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
@ -228,7 +230,9 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("select drum bank", () => AddStep("select drum bank", () =>
{ {
InputManager.PressKey(Key.LShift); InputManager.PressKey(Key.LShift);
InputManager.PressKey(Key.LAlt);
InputManager.Key(Key.R); InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.LAlt);
InputManager.ReleaseKey(Key.LShift); InputManager.ReleaseKey(Key.LShift);
}); });
AddStep("enable clap addition", () => InputManager.Key(Key.R)); AddStep("enable clap addition", () => InputManager.Key(Key.R));

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Rulesets.Edit
{
internal partial class ExpandableSpriteText : OsuSpriteText, IExpandable
{
public BindableBool Expanded { get; } = new BindableBool();
[Resolved(canBeNull: true)]
private IExpandingContainer? expandingContainer { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
expandingContainer?.Expanded.BindValueChanged(containerExpanded =>
{
Expanded.Value = containerExpanded.NewValue;
}, true);
Expanded.BindValueChanged(expanded =>
{
this.FadeTo(expanded.NewValue ? 1 : 0, 150, Easing.OutQuint);
}, true);
}
}
}

View File

@ -18,6 +18,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
@ -178,9 +179,47 @@ namespace osu.Game.Rulesets.Edit
Spacing = new Vector2(0, 5), Spacing = new Vector2(0, 5),
}, },
}, },
new EditorToolboxGroup("bank (Shift-Q~R)") new EditorToolboxGroup("bank (Shift/Alt-Q~R)")
{ {
Child = sampleBankTogglesCollection = new FillFlowContainer Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new ExpandableSpriteText
{
Text = "Normal",
AlwaysPresent = true,
AllowMultiline = false,
RelativePositionAxes = Axes.X,
X = 0.25f,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopLeft,
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17),
},
new ExpandableSpriteText
{
Text = "Addition",
AlwaysPresent = true,
AllowMultiline = false,
RelativePositionAxes = Axes.X,
X = 0.75f,
Origin = Anchor.TopCentre,
Anchor = Anchor.TopLeft,
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 17),
},
}
},
sampleBankTogglesCollection = new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -192,6 +231,8 @@ namespace osu.Game.Rulesets.Edit
}, },
} }
}, },
}
},
new Container new Container
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -231,7 +272,7 @@ namespace osu.Game.Rulesets.Edit
TernaryStates = CreateTernaryButtons().ToArray(); TernaryStates = CreateTernaryButtons().ToArray();
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b))); togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b))); sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new SampleBankTernaryButton(b.First, b.Second)));
SetSelectTool(); SetSelectTool();
@ -362,7 +403,7 @@ namespace osu.Game.Rulesets.Edit
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (e.ControlPressed || e.AltPressed || e.SuperPressed) if (e.ControlPressed || e.SuperPressed)
return false; return false;
if (checkToolboxMappingFromKey(e.Key, out int leftIndex)) if (checkToolboxMappingFromKey(e.Key, out int leftIndex))
@ -379,16 +420,28 @@ namespace osu.Game.Rulesets.Edit
if (checkToggleMappingFromKey(e.Key, out int rightIndex)) if (checkToggleMappingFromKey(e.Key, out int rightIndex))
{ {
var item = e.ShiftPressed if (e.ShiftPressed || e.AltPressed)
? sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) {
: togglesCollection.ElementAtOrDefault(rightIndex); if (sampleBankTogglesCollection.ElementAtOrDefault(rightIndex) is SampleBankTernaryButton sampleBankTernaryButton)
{
if (e.ShiftPressed)
sampleBankTernaryButton.NormalButton.Toggle();
if (item is DrawableTernaryButton button) if (e.AltPressed)
sampleBankTernaryButton.AdditionsButton.Toggle();
return true;
}
}
else
{
if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button)
{ {
button.Button.Toggle(); button.Button.Toggle();
return true; return true;
} }
} }
}
return base.OnKeyDown(e); return base.OnKeyDown(e);
} }

View File

@ -25,6 +25,11 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public bool AutomaticBankAssignment { get; set; } public bool AutomaticBankAssignment { get; set; }
/// <summary>
/// Whether the sample addition bank should be taken from the previous hit objects.
/// </summary>
public bool AutomaticAdditionBankAssignment { get; set; }
/// <summary> /// <summary>
/// The <see cref="HitObject"/> that is being placed. /// The <see cref="HitObject"/> that is being placed.
/// </summary> /// </summary>
@ -73,7 +78,7 @@ namespace osu.Game.Rulesets.Edit
} }
/// <summary> /// <summary>
/// Updates the time and position of this <see cref="HitObjectPlacementBlueprint"/> based on the provided snap information. /// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
/// </summary> /// </summary>
/// <param name="result">The snap result information.</param> /// <param name="result">The snap result information.</param>
public override void UpdateTimeAndPosition(SnapResult result) public override void UpdateTimeAndPosition(SnapResult result)
@ -87,26 +92,26 @@ namespace osu.Game.Rulesets.Edit
} }
var lastHitObject = getPreviousHitObject(); var lastHitObject = getPreviousHitObject();
if (AutomaticBankAssignment)
{
// Create samples based on the sample settings of the previous hit object
if (lastHitObject != null)
{
for (int i = 0; i < HitObject.Samples.Count; i++)
HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name);
}
}
else
{
var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); var lastHitNormal = lastHitObject?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL);
if (AutomaticAdditionBankAssignment)
{
// Inherit the addition bank from the previous hit object
// If there is no previous addition, inherit from the normal sample
var lastAddition = lastHitObject?.Samples?.FirstOrDefault(o => o.Name != HitSampleInfo.HIT_NORMAL) ?? lastHitNormal;
if (lastAddition != null)
HitObject.Samples = HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastAddition.Bank) : s).ToList();
}
if (lastHitNormal != null) if (lastHitNormal != null)
{ {
// Only inherit the volume from the previous hit object if (AutomaticBankAssignment)
for (int i = 0; i < HitObject.Samples.Count; i++) // Inherit the bank from the previous hit object
HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); HitObject.Samples = HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: lastHitNormal.Bank) : s).ToList();
}
// Inherit the volume from the previous hit object
HitObject.Samples = HitObject.Samples.Select(s => s.With(newVolume: lastHitNormal.Volume)).ToList();
} }
if (HitObject is IHasRepeats hasRepeats) if (HitObject is IHasRepeats hasRepeats)

View File

@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
private Color4 selectedBackgroundColour; private Color4 selectedBackgroundColour;
private Color4 selectedIconColour; private Color4 selectedIconColour;
private Drawable icon = null!; protected Drawable Icon { get; private set; } = null!;
public readonly TernaryButton Button; public readonly TernaryButton Button;
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
defaultIconColour = defaultBackgroundColour.Darken(0.5f); defaultIconColour = defaultBackgroundColour.Darken(0.5f);
selectedIconColour = selectedBackgroundColour.Lighten(0.5f); selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => Add(Icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
{ {
b.Blending = BlendingParameters.Additive; b.Blending = BlendingParameters.Additive;
b.Anchor = Anchor.CentreLeft; b.Anchor = Anchor.CentreLeft;
@ -75,17 +75,17 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
switch (Button.Bindable.Value) switch (Button.Bindable.Value)
{ {
case TernaryState.Indeterminate: case TernaryState.Indeterminate:
icon.Colour = selectedIconColour.Darken(0.5f); Icon.Colour = selectedIconColour.Darken(0.5f);
BackgroundColour = selectedBackgroundColour.Darken(0.5f); BackgroundColour = selectedBackgroundColour.Darken(0.5f);
break; break;
case TernaryState.False: case TernaryState.False:
icon.Colour = defaultIconColour; Icon.Colour = defaultIconColour;
BackgroundColour = defaultBackgroundColour; BackgroundColour = defaultBackgroundColour;
break; break;
case TernaryState.True: case TernaryState.True:
icon.Colour = selectedIconColour; Icon.Colour = selectedIconColour;
BackgroundColour = selectedBackgroundColour; BackgroundColour = selectedBackgroundColour;
break; break;
} }

View File

@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Edit;
namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
public partial class SampleBankTernaryButton : CompositeDrawable
{
public readonly TernaryButton NormalButton;
public readonly TernaryButton AdditionsButton;
public SampleBankTernaryButton(TernaryButton normalButton, TernaryButton additionsButton)
{
NormalButton = normalButton;
AdditionsButton = additionsButton;
}
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Masking = true;
CornerRadius = 5;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Padding = new MarginPadding { Right = 1 },
Child = new InlineDrawableTernaryButton(NormalButton),
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Padding = new MarginPadding { Left = 1 },
Child = new InlineDrawableTernaryButton(AdditionsButton),
},
};
}
private partial class InlineDrawableTernaryButton : DrawableTernaryButton
{
public InlineDrawableTernaryButton(TernaryButton button)
: base(button)
{
}
[BackgroundDependencyLoader]
private void load()
{
Content.Masking = false;
Content.CornerRadius = 0;
Icon.X = 4.5f;
}
protected override SpriteText CreateText() => new ExpandableSpriteText
{
Depth = -1,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
X = 31f
};
}
}
}

View File

@ -65,7 +65,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void load() private void load()
{ {
MainTernaryStates = CreateTernaryButtons().ToArray(); MainTernaryStates = CreateTernaryButtons().ToArray();
SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray(); SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray();
SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray();
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset) AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
{ {
@ -91,6 +92,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var kvp in SelectionHandler.SelectionBankStates) foreach (var kvp in SelectionHandler.SelectionBankStates)
kvp.Value.BindValueChanged(_ => updatePlacementSamples()); kvp.Value.BindValueChanged(_ => updatePlacementSamples());
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
} }
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject) protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
@ -179,6 +183,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var kvp in SelectionHandler.SelectionBankStates) foreach (var kvp in SelectionHandler.SelectionBankStates)
bankChanged(kvp.Key, kvp.Value.Value); bankChanged(kvp.Key, kvp.Value.Value);
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
additionBankChanged(kvp.Key, kvp.Value.Value);
} }
private void sampleChanged(string sampleName, TernaryState state) private void sampleChanged(string sampleName, TernaryState state)
@ -210,7 +217,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO) if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True; CurrentHitObjectPlacement.AutomaticBankAssignment = state == TernaryState.True;
else if (state == TernaryState.True) else if (state == TernaryState.True)
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.With(newBank: bankName)).ToList(); CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
}
private void additionBankChanged(string bankName, TernaryState state)
{
if (CurrentHitObjectPlacement == null) return;
if (bankName == EditorSelectionHandler.HIT_BANK_AUTO)
CurrentHitObjectPlacement.AutomaticAdditionBankAssignment = state == TernaryState.True;
else if (state == TernaryState.True)
CurrentHitObjectPlacement.HitObject.Samples = CurrentHitObjectPlacement.HitObject.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
} }
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" }; public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
@ -222,6 +239,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
public TernaryButton[] SampleBankTernaryStates { get; private set; } public TernaryButton[] SampleBankTernaryStates { get; private set; }
public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; }
/// <summary> /// <summary>
/// Create all ternary states required to be displayed to the user. /// Create all ternary states required to be displayed to the user.
/// </summary> /// </summary>
@ -234,36 +253,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key)); yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
} }
private IEnumerable<TernaryButton> createSampleBankTernaryButtons() private IEnumerable<TernaryButton> createSampleBankTernaryButtons(Dictionary<string, Bindable<TernaryState>> sampleBankStates)
{ {
foreach (var kvp in SelectionHandler.SelectionBankStates) foreach (var kvp in sampleBankStates)
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key)); yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
} }
private Drawable getIconForBank(string sampleName) private Drawable getIconForBank(string sampleName)
{ {
return new Container return new OsuSpriteText
{ {
Size = new Vector2(30, 20), Anchor = Anchor.Centre,
Children = new Drawable[] Origin = Anchor.Centre,
{
new SpriteIcon
{
Size = new Vector2(8),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = FontAwesome.Solid.VolumeOff
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
X = 10,
Y = -1, Y = -1,
Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20), Font = OsuFont.Default.With(weight: FontWeight.Bold, size: 20),
Text = $"{char.ToUpperInvariant(sampleName.First())}" Text = $"{char.ToUpperInvariant(sampleName.First())}"
}
}
}; };
} }

View File

@ -59,6 +59,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
public readonly Dictionary<string, Bindable<TernaryState>> SelectionBankStates = new Dictionary<string, Bindable<TernaryState>>(); public readonly Dictionary<string, Bindable<TernaryState>> SelectionBankStates = new Dictionary<string, Bindable<TernaryState>>();
/// <summary>
/// The state of each sample addition bank type for all selected hitobjects.
/// </summary>
public readonly Dictionary<string, Bindable<TernaryState>> SelectionAdditionBankStates = new Dictionary<string, Bindable<TernaryState>>();
/// <summary> /// <summary>
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions) /// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
/// </summary> /// </summary>
@ -91,7 +96,7 @@ 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.All(h => h.Samples.All(s => s.Bank == bankName))) if (SelectedItems.All(h => h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName)))
bindable.Value = TernaryState.True; bindable.Value = TernaryState.True;
} }
@ -128,8 +133,84 @@ namespace osu.Game.Screens.Edit.Compose.Components
SelectionBankStates[bankName] = bindable; SelectionBankStates[bankName] = bindable;
} }
foreach (string bankName in HitSampleInfo.AllBanks.Prepend(HIT_BANK_AUTO))
{
var bindable = new Bindable<TernaryState>
{
Description = bankName.Titleize()
};
bindable.ValueChanged += state =>
{
switch (state.NewValue)
{
case TernaryState.False:
if (SelectedItems.Count == 0)
{
// Ensure that if this is the last selected bank, it should remain selected.
if (SelectionAdditionBankStates.Values.All(b => b.Value == TernaryState.False))
bindable.Value = TernaryState.True;
}
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.
// 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)))
break;
// Never remove a sample bank.
// 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)))
bindable.Value = TernaryState.True;
}
break;
case TernaryState.True:
if (SelectedItems.Count == 0)
{
// Ensure the user can't stack multiple bank selections when there's no hitobject selection.
// Note that in normal scenarios this is sorted out by the feedback from applying the bank to the selected objects.
foreach (var other in SelectionAdditionBankStates.Values)
{
if (other != bindable)
other.Value = TernaryState.False;
}
}
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 (SelectedItems.SelectMany(enumerateAllSamples).All(h => h.All(o => o.Name == HitSampleInfo.HIT_NORMAL)))
{
bindable.Value = TernaryState.False;
break;
}
SetSampleAdditionBank(bankName);
}
break;
}
};
SelectionAdditionBankStates[bankName] = bindable;
}
// 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;
foreach (string sampleName in HitSampleInfo.AllAdditions) foreach (string sampleName in HitSampleInfo.AllAdditions)
{ {
@ -187,10 +268,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach ((string bankName, var bindable) in SelectionBankStates) foreach ((string bankName, var bindable) in SelectionBankStates)
{ {
bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s), h => h.Bank == bankName); bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s).Where(o => o.Name == HitSampleInfo.HIT_NORMAL), h => h.Bank == bankName);
} }
IEnumerable<IList<HitSampleInfo>> enumerateAllSamples(HitObject hitObject) 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);
}
}
private IEnumerable<IList<HitSampleInfo>> enumerateAllSamples(HitObject hitObject)
{ {
yield return hitObject.Samples; yield return hitObject.Samples;
@ -200,7 +287,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
yield return node; yield return node;
} }
} }
}
#endregion #endregion
@ -214,12 +300,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
bool hasRelevantBank(HitObject hitObject) bool hasRelevantBank(HitObject hitObject)
{ {
bool result = hitObject.Samples.All(s => s.Bank == bankName); bool result = hitObject.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
if (hitObject is IHasRepeats hasRepeats) if (hitObject is IHasRepeats hasRepeats)
{ {
foreach (var node in hasRepeats.NodeSamples) foreach (var node in hasRepeats.NodeSamples)
result &= node.All(s => s.Bank == bankName); result &= node.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
} }
return result; return result;
@ -233,12 +319,51 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (hasRelevantBank(h)) if (hasRelevantBank(h))
return; return;
h.Samples = h.Samples.Select(s => s.With(newBank: bankName)).ToList(); h.Samples = h.Samples.Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : 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.With(newBank: bankName)).ToList(); hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(s => s.Name == HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
}
EditorBeatmap.Update(h);
});
}
/// <summary>
/// Sets the sample addition bank for all selected <see cref="HitObject"/>s.
/// </summary>
/// <param name="bankName">The name of the sample bank.</param>
public void SetSampleAdditionBank(string bankName)
{
bool hasRelevantBank(HitObject hitObject)
{
bool result = hitObject.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName);
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))
return;
EditorBeatmap.PerformOnSelection(h =>
{
if (enumerateAllSamples(h).SelectMany(o => o).Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(s => s.Bank == bankName))
return;
h.Samples = h.Samples.Select(s => s.Name != HitSampleInfo.HIT_NORMAL ? s.With(newBank: bankName) : s).ToList();
if (h is IHasRepeats hasRepeats)
{
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();
} }
EditorBeatmap.Update(h); EditorBeatmap.Update(h);
@ -415,6 +540,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
State = { BindTarget = drum }, State = { BindTarget = drum },
Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.R)) Hotkey = new Hotkey(new KeyCombination(InputKey.Shift, InputKey.R))
}; };
yield return new OsuMenuItem("Addition bank")
{
Items = SelectionAdditionBankStates.Select(kvp =>
new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
};
} }
#endregion #endregion

View File

@ -438,21 +438,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (e.ControlPressed || e.AltPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex)) if (e.ControlPressed || e.SuperPressed || !checkRightToggleFromKey(e.Key, out int rightIndex))
return base.OnKeyDown(e); return base.OnKeyDown(e);
if (e.ShiftPressed) if (e.ShiftPressed || e.AltPressed)
{ {
string? newBank = banks.ElementAtOrDefault(rightIndex); string? newBank = banks.ElementAtOrDefault(rightIndex);
if (string.IsNullOrEmpty(newBank)) if (string.IsNullOrEmpty(newBank))
return true; return true;
if (e.ShiftPressed)
{
setBank(newBank); setBank(newBank);
updatePrimaryBankState(); updatePrimaryBankState();
}
if (e.AltPressed)
{
setAdditionBank(newBank); setAdditionBank(newBank);
updateAdditionBankState(); updateAdditionBankState();
} }
}
else else
{ {
var item = togglesCollection.ElementAtOrDefault(rightIndex); var item = togglesCollection.ElementAtOrDefault(rightIndex);