1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-23 08:27:23 +08:00

Merge pull request #31462 from bdach/ternary-button-is-bad

Simplify editor "ternary button" structure
This commit is contained in:
Dan Balasescu 2025-01-10 14:24:41 +09:00 committed by GitHub
commit 38d95abc24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 147 additions and 117 deletions

View File

@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit
protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider);
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
protected override IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
=> base.CreateTernaryButtons()
.Concat(DistanceSnapProvider.CreateTernaryButtons());

View File

@ -53,9 +53,14 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
protected override IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
=> base.CreateTernaryButtons()
.Append(new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }))
.Append(new DrawableTernaryButton
{
Current = rectangularGridSnapToggle,
Description = "Grid Snap",
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap },
})
.Concat(DistanceSnapProvider.CreateTernaryButtons());
private BindableList<HitObject> selectedHitObjects;

View File

@ -191,9 +191,14 @@ namespace osu.Game.Rulesets.Edit
}
}
public IEnumerable<TernaryButton> CreateTernaryButtons() => new[]
public IEnumerable<DrawableTernaryButton> CreateTernaryButtons() => new[]
{
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap })
new DrawableTernaryButton
{
Current = DistanceSnapToggle,
Description = "Distance Snap",
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap },
}
};
public void HandleToggleViaKey(KeyboardEvent key)

View File

@ -269,10 +269,9 @@ namespace osu.Game.Rulesets.Edit
};
}
TernaryStates = CreateTernaryButtons().ToArray();
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
togglesCollection.AddRange(CreateTernaryButtons().ToArray());
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new SampleBankTernaryButton(b.First, b.Second)));
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates);
SetSelectTool();
@ -368,15 +367,10 @@ namespace osu.Game.Rulesets.Edit
/// </remarks>
protected abstract IReadOnlyList<CompositionTool> CompositionTools { get; }
/// <summary>
/// A collection of states which will be displayed to the user in the toolbox.
/// </summary>
public TernaryButton[] TernaryStates { get; private set; }
/// <summary>
/// Create all ternary states required to be displayed to the user.
/// </summary>
protected virtual IEnumerable<TernaryButton> CreateTernaryButtons() => BlueprintContainer.MainTernaryStates;
protected virtual IEnumerable<DrawableTernaryButton> CreateTernaryButtons() => BlueprintContainer.MainTernaryStates;
/// <summary>
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
@ -437,7 +431,7 @@ namespace osu.Game.Rulesets.Edit
{
if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button)
{
button.Button.Toggle();
button.Toggle();
return true;
}
}

View File

@ -56,7 +56,12 @@ namespace osu.Game.Rulesets.Edit
Spacing = new Vector2(0, 5),
Children = new[]
{
new DrawableTernaryButton(new TernaryButton(showSpeedChanges, "Show speed changes", () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt }))
new DrawableTernaryButton
{
Current = showSpeedChanges,
Description = "Show speed changes",
CreateIcon = () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt },
}
}
},
});

View File

@ -1,12 +1,15 @@
// 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 System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@ -16,8 +19,29 @@ using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
public partial class DrawableTernaryButton : OsuButton, IHasTooltip
public partial class DrawableTernaryButton : OsuButton, IHasTooltip, IHasCurrentValue<TernaryState>
{
public Bindable<TernaryState> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly BindableWithCurrent<TernaryState> current = new BindableWithCurrent<TernaryState>();
public required LocalisableString Description
{
get => Text;
set => Text = value;
}
public LocalisableString TooltipText { get; set; }
/// <summary>
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
/// </summary>
public Func<Drawable>? CreateIcon { get; init; }
private Color4 defaultBackgroundColour;
private Color4 defaultIconColour;
private Color4 selectedBackgroundColour;
@ -25,14 +49,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
protected Drawable Icon { get; private set; } = null!;
public readonly TernaryButton Button;
public DrawableTernaryButton(TernaryButton button)
public DrawableTernaryButton()
{
Button = button;
Text = button.Description;
RelativeSizeAxes = Axes.X;
}
@ -45,7 +63,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
defaultIconColour = defaultBackgroundColour.Darken(0.5f);
selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
Add(Icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
Add(Icon = (CreateIcon?.Invoke() ?? new Circle()).With(b =>
{
b.Blending = BlendingParameters.Additive;
b.Anchor = Anchor.CentreLeft;
@ -59,18 +77,32 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
base.LoadComplete();
Button.Bindable.BindValueChanged(_ => updateSelectionState(), true);
Button.Enabled.BindTo(Enabled);
current.BindValueChanged(_ => updateSelectionState(), true);
Action = onAction;
}
private void onAction()
{
if (!Button.Enabled.Value)
if (!Enabled.Value)
return;
Button.Toggle();
Toggle();
}
public void Toggle()
{
switch (Current.Value)
{
case TernaryState.False:
case TernaryState.Indeterminate:
Current.Value = TernaryState.True;
break;
case TernaryState.True:
Current.Value = TernaryState.False;
break;
}
}
private void updateSelectionState()
@ -78,7 +110,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
if (!IsLoaded)
return;
switch (Button.Bindable.Value)
switch (Current.Value)
{
case TernaryState.Indeterminate:
Icon.Colour = selectedIconColour.Darken(0.5f);
@ -104,7 +136,5 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
Anchor = Anchor.CentreLeft,
X = 40f
};
public LocalisableString TooltipText => Button.Tooltip;
}
}

View File

@ -1,23 +1,32 @@
// 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 System;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
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 string BankName { get; }
public Func<Drawable>? CreateIcon { get; init; }
public SampleBankTernaryButton(TernaryButton normalButton, TernaryButton additionsButton)
public readonly BindableWithCurrent<TernaryState> NormalState = new BindableWithCurrent<TernaryState>();
public readonly BindableWithCurrent<TernaryState> AdditionsState = new BindableWithCurrent<TernaryState>();
public DrawableTernaryButton NormalButton { get; private set; } = null!;
public DrawableTernaryButton AdditionsButton { get; private set; } = null!;
public SampleBankTernaryButton(string bankName)
{
NormalButton = normalButton;
AdditionsButton = additionsButton;
BankName = bankName;
}
[BackgroundDependencyLoader]
@ -36,7 +45,12 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Padding = new MarginPadding { Right = 1 },
Child = new InlineDrawableTernaryButton(NormalButton),
Child = NormalButton = new InlineDrawableTernaryButton
{
Current = NormalState,
Description = BankName.Titleize(),
CreateIcon = CreateIcon,
},
},
new Container
{
@ -46,18 +60,18 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Padding = new MarginPadding { Left = 1 },
Child = new InlineDrawableTernaryButton(AdditionsButton),
Child = AdditionsButton = new InlineDrawableTernaryButton
{
Current = AdditionsState,
Description = BankName.Titleize(),
CreateIcon = CreateIcon,
},
},
};
}
private partial class InlineDrawableTernaryButton : DrawableTernaryButton
{
public InlineDrawableTernaryButton(TernaryButton button)
: base(button)
{
}
[BackgroundDependencyLoader]
private void load()
{

View File

@ -1,48 +0,0 @@
// 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 System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
public class TernaryButton
{
public readonly Bindable<TernaryState> Bindable;
public readonly Bindable<bool> Enabled = new Bindable<bool>(true);
public readonly string Description;
/// <summary>
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
/// </summary>
public readonly Func<Drawable>? CreateIcon;
public string Tooltip { get; set; } = string.Empty;
public TernaryButton(Bindable<TernaryState> bindable, string description, Func<Drawable>? createIcon = null)
{
Bindable = bindable;
Description = description;
CreateIcon = createIcon;
}
public void Toggle()
{
switch (Bindable.Value)
{
case TernaryState.False:
case TernaryState.Indeterminate:
Bindable.Value = TernaryState.True;
break;
case TernaryState.True:
Bindable.Value = TernaryState.False;
break;
}
}
}
}

View File

@ -65,11 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void load()
{
MainTernaryStates = CreateTernaryButtons().ToArray();
SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray();
SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray();
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray();
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
{
@ -98,6 +94,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
}
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
@ -238,28 +237,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary>
/// A collection of states which will be displayed to the user in the toolbox.
/// </summary>
public TernaryButton[] MainTernaryStates { get; private set; }
public DrawableTernaryButton[] MainTernaryStates { get; private set; }
public TernaryButton[] SampleBankTernaryStates { get; private set; }
public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; }
public SampleBankTernaryButton[] SampleBankTernaryStates { get; private set; }
/// <summary>
/// Create all ternary states required to be displayed to the user.
/// </summary>
protected virtual IEnumerable<TernaryButton> CreateTernaryButtons()
protected virtual IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
{
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA });
yield return new DrawableTernaryButton
{
Current = NewCombo,
Description = "New combo",
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA },
};
foreach (var kvp in SelectionHandler.SelectionSampleStates)
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
{
yield return new DrawableTernaryButton
{
Current = kvp.Value,
Description = kvp.Key.Replace(@"hit", string.Empty).Titleize(),
CreateIcon = () => GetIconForSample(kvp.Key),
};
}
}
private IEnumerable<TernaryButton> createSampleBankTernaryButtons(Dictionary<string, Bindable<TernaryState>> sampleBankStates)
private IEnumerable<SampleBankTernaryButton> createSampleBankTernaryButtons()
{
foreach (var kvp in sampleBankStates)
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
foreach (string bankName in HitSampleInfo.ALL_BANKS.Prepend(EditorSelectionHandler.HIT_BANK_AUTO))
{
yield return new SampleBankTernaryButton(bankName)
{
NormalState = { Current = SelectionHandler.SelectionBankStates[bankName], },
AdditionsState = { Current = SelectionHandler.SelectionAdditionBankStates[bankName], },
CreateIcon = () => getIconForBank(bankName)
};
}
}
private Drawable getIconForBank(string sampleName)
@ -295,19 +311,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
bool enabled = SelectionHandler.AutoSelectionBankEnabled.Value;
var autoBankButton = SampleBankTernaryStates.Single(t => t.Bindable == SelectionHandler.SelectionBankStates[EditorSelectionHandler.HIT_BANK_AUTO]);
autoBankButton.Enabled.Value = enabled;
autoBankButton.Tooltip = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
var autoBankButton = SampleBankTernaryStates.Single(t => t.BankName == EditorSelectionHandler.HIT_BANK_AUTO);
autoBankButton.NormalButton.Enabled.Value = enabled;
autoBankButton.NormalButton.TooltipText = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
}
private void updateAdditionBankTernaryButtonTooltips()
{
bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value;
foreach (var ternaryButton in SampleAdditionBankTernaryStates)
foreach (var ternaryButton in SampleBankTernaryStates)
{
ternaryButton.Enabled.Value = enabled;
ternaryButton.Tooltip = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
ternaryButton.AdditionsButton.Enabled.Value = enabled;
ternaryButton.AdditionsButton.TooltipText = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
}
}

View File

@ -300,7 +300,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
createStateBindables();
updateTernaryStates();
togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) }));
togglesCollection.AddRange(createTernaryButtons());
}
private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1
@ -444,10 +444,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
}
private IEnumerable<TernaryButton> createTernaryButtons()
private IEnumerable<DrawableTernaryButton> createTernaryButtons()
{
foreach ((string sampleName, var bindable) in selectionSampleStates)
yield return new TernaryButton(bindable, string.Empty, () => ComposeBlueprintContainer.GetIconForSample(sampleName));
{
yield return new DrawableTernaryButton
{
Current = bindable,
Description = string.Empty,
CreateIcon = () => ComposeBlueprintContainer.GetIconForSample(sampleName),
RelativeSizeAxes = Axes.None,
Size = new Vector2(40, 40),
};
}
}
private void addHitSample(string sampleName)
@ -516,7 +525,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
button.Button.Toggle();
button.Toggle();
}
return true;