1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:07:52 +08:00

Add ternary toggle buttons to editor toolbox selection

This commit is contained in:
Dean Herbert 2020-09-25 16:33:22 +09:00
parent 0f8551e9ea
commit ae68dcd962
6 changed files with 194 additions and 38 deletions

View File

@ -9,7 +9,9 @@ using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
@ -17,6 +19,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@ -39,11 +42,11 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool()
};
private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" };
private readonly Bindable<TernaryState> distanceSnapToggle = new Bindable<TernaryState>();
protected override IEnumerable<Bindable<bool>> Toggles => base.Toggles.Concat(new[]
protected override IEnumerable<TernaryButton> Toggles => base.Toggles.Concat(new[]
{
distanceSnapToggle
new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler })
});
private BindableList<HitObject> selectedHitObjects;
@ -156,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Edit
distanceSnapGridCache.Invalidate();
distanceSnapGrid = null;
if (!distanceSnapToggle.Value)
if (distanceSnapToggle.Value != TernaryState.True)
return;
switch (BlueprintContainer.CurrentTool)

View File

@ -14,7 +14,6 @@ using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
@ -24,6 +23,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.RadioButtons;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@ -124,11 +124,7 @@ namespace osu.Game.Rulesets.Edit
new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } },
togglesCollection = new ToolboxGroup("toggles")
{
ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox
{
Bindable = b,
LabelText = b?.Description ?? "unknown"
})
ChildrenEnumerable = Toggles.Select(b => new DrawableTernaryButton(b))
}
}
},
@ -170,7 +166,7 @@ namespace osu.Game.Rulesets.Edit
/// A collection of toggles which will be displayed to the user.
/// The display name will be decided by <see cref="Bindable{T}.Description"/>.
/// </summary>
protected virtual IEnumerable<Bindable<bool>> Toggles => BlueprintContainer.Toggles;
protected virtual IEnumerable<TernaryButton> Toggles => BlueprintContainer.Toggles;
/// <summary>
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
@ -212,9 +208,9 @@ namespace osu.Game.Rulesets.Edit
{
var item = togglesCollection.Children[rightIndex];
if (item is SettingsCheckbox checkbox)
if (item is DrawableTernaryButton button)
{
checkbox.Bindable.Value = !checkbox.Bindable.Value;
button.Button.Toggle();
return true;
}
}

View File

@ -0,0 +1,112 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components.TernaryButtons
{
internal class DrawableTernaryButton : TriangleButton
{
private Color4 defaultBackgroundColour;
private Color4 defaultBubbleColour;
private Color4 selectedBackgroundColour;
private Color4 selectedBubbleColour;
private Drawable icon;
public readonly TernaryButton Button;
public DrawableTernaryButton(TernaryButton button)
{
Button = button;
Text = button.Description;
RelativeSizeAxes = Axes.X;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
defaultBackgroundColour = colours.Gray3;
defaultBubbleColour = defaultBackgroundColour.Darken(0.5f);
selectedBackgroundColour = colours.BlueDark;
selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f);
Triangles.Alpha = 0;
Content.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Radius = 2,
Offset = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f)
};
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
{
b.Blending = BlendingParameters.Additive;
b.Anchor = Anchor.CentreLeft;
b.Origin = Anchor.CentreLeft;
b.Size = new Vector2(20);
b.X = 10;
}));
}
protected override void LoadComplete()
{
base.LoadComplete();
Button.Bindable.BindValueChanged(selected => updateSelectionState(), true);
Action = onAction;
}
private void onAction()
{
Button.Toggle();
}
private void updateSelectionState()
{
if (!IsLoaded)
return;
switch (Button.Bindable.Value)
{
case TernaryState.Indeterminate:
icon.Colour = selectedBubbleColour.Darken(0.5f);
BackgroundColour = selectedBackgroundColour.Darken(0.5f);
break;
case TernaryState.False:
icon.Colour = defaultBubbleColour;
BackgroundColour = defaultBackgroundColour;
break;
case TernaryState.True:
icon.Colour = selectedBubbleColour;
BackgroundColour = selectedBackgroundColour;
break;
}
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
X = 40f
};
}
}

View File

@ -0,0 +1,44 @@
// 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 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 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

@ -7,12 +7,16 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
@ -57,35 +61,26 @@ namespace osu.Game.Screens.Edit.Compose.Components
inputManager = GetContainingInputManager();
Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection();
// the updated object may be in the selection
Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection();
// updates to selected are handled for us by SelectionHandler.
NewCombo.BindTo(SelectionHandler.SelectionNewComboState);
// we are responsible for current placement blueprint updated based on state changes.
NewCombo.ValueChanged += combo =>
{
if (Beatmap.SelectedHitObjects.Count > 0)
if (currentPlacement != null)
{
SelectionHandler.SetNewCombo(combo.NewValue);
}
else if (currentPlacement != null)
{
// update placement object from toggle
if (currentPlacement.HitObject is IHasComboInformation c)
c.NewCombo = combo.NewValue;
c.NewCombo = combo.NewValue == TernaryState.True;
}
};
}
private void updateTogglesFromSelection() =>
NewCombo.Value = Beatmap.SelectedHitObjects.OfType<IHasComboInformation>().All(c => c.NewCombo);
public readonly Bindable<TernaryState> NewCombo = new Bindable<TernaryState> { Description = "New Combo" };
public readonly Bindable<bool> NewCombo = new Bindable<bool> { Description = "New Combo" };
public virtual IEnumerable<Bindable<bool>> Toggles => new[]
public virtual IEnumerable<TernaryButton> Toggles => new[]
{
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
NewCombo
new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle })
};
#region Placement

View File

@ -316,9 +316,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
#region Selection State
private readonly Bindable<TernaryState> selectionNewComboState = new Bindable<TernaryState>();
/// <summary>
/// The state of "new combo" for all selected hitobjects.
/// </summary>
public readonly Bindable<TernaryState> SelectionNewComboState = new Bindable<TernaryState>();
private readonly Dictionary<string, Bindable<TernaryState>> selectionSampleStates = new Dictionary<string, Bindable<TernaryState>>();
/// <summary>
/// The state of each sample type for all selected hitobjects. Keys match with <see cref="HitSampleInfo"/> constant specifications.
/// </summary>
public readonly Dictionary<string, Bindable<TernaryState>> SelectionSampleStates = new Dictionary<string, Bindable<TernaryState>>();
/// <summary>
/// Set up ternary state bindables and bind them to selection/hitobject changes (in both directions)
@ -349,11 +355,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
}
};
selectionSampleStates[sampleName] = bindable;
SelectionSampleStates[sampleName] = bindable;
}
// new combo
selectionNewComboState.ValueChanged += state =>
SelectionNewComboState.ValueChanged += state =>
{
switch (state.NewValue)
{
@ -377,9 +383,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary>
protected virtual void UpdateTernaryStates()
{
selectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType<IHasComboInformation>(), h => h.NewCombo);
SelectionNewComboState.Value = GetStateFromSelection(SelectedHitObjects.OfType<IHasComboInformation>(), h => h.NewCombo);
foreach (var (sampleName, bindable) in selectionSampleStates)
foreach (var (sampleName, bindable) in SelectionSampleStates)
{
bindable.Value = GetStateFromSelection(SelectedHitObjects, h => h.Samples.Any(s => s.Name == sampleName));
}
@ -413,7 +419,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation))
{
items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = selectionNewComboState } });
items.Add(new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } });
}
if (selectedBlueprints.Count == 1)
@ -423,7 +429,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
new OsuMenuItem("Sound")
{
Items = selectionSampleStates.Select(kvp =>
Items = SelectionSampleStates.Select(kvp =>
new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray()
},
new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected),