1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 21:32:57 +08:00

Merge pull request #10239 from peppy/new-combo-toggle

This commit is contained in:
Dean Herbert 2020-09-25 18:30:46 +09:00 committed by GitHub
commit 8bbf15a096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 77 additions and 36 deletions

View File

@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" };
protected override IEnumerable<BindableBool> Toggles => new[] protected override IEnumerable<Bindable<bool>> Toggles => base.Toggles.Concat(new[]
{ {
distanceSnapToggle distanceSnapToggle
}; });
private BindableList<HitObject> selectedHitObjects; private BindableList<HitObject> selectedHitObjects;

View File

@ -31,6 +31,11 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
{ {
/// <summary>
/// Top level container for editor compose mode.
/// Responsible for providing snapping and generally gluing components together.
/// </summary>
/// <typeparam name="TObject">The base type of supported objects.</typeparam>
[Cached(Type = typeof(IPlacementHandler))] [Cached(Type = typeof(IPlacementHandler))]
public abstract class HitObjectComposer<TObject> : HitObjectComposer, IPlacementHandler public abstract class HitObjectComposer<TObject> : HitObjectComposer, IPlacementHandler
where TObject : HitObject where TObject : HitObject
@ -165,7 +170,7 @@ namespace osu.Game.Rulesets.Edit
/// A collection of toggles which will be displayed to the user. /// A collection of toggles which will be displayed to the user.
/// The display name will be decided by <see cref="Bindable{T}.Description"/>. /// The display name will be decided by <see cref="Bindable{T}.Description"/>.
/// </summary> /// </summary>
protected virtual IEnumerable<BindableBool> Toggles => Enumerable.Empty<BindableBool>(); protected virtual IEnumerable<Bindable<bool>> Toggles => BlueprintContainer.Toggles;
/// <summary> /// <summary>
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// The <see cref="HitObject"/> that is being placed. /// The <see cref="HitObject"/> that is being placed.
/// </summary> /// </summary>
protected readonly HitObject HitObject; public readonly HitObject HitObject;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
protected EditorClock EditorClock { get; private set; } protected EditorClock EditorClock { get; private set; }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
/// <summary> /// <summary>
/// A container which provides a "blueprint" display of hitobjects. /// A container which provides a "blueprint" display of hitobjects.
/// Includes selection and manipulation support via a <see cref="SelectionHandler"/>. /// Includes selection and manipulation support via a <see cref="Components.SelectionHandler"/>.
/// </summary> /// </summary>
public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler<PlatformAction> public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler<PlatformAction>
{ {
@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public Container<SelectionBlueprint> SelectionBlueprints { get; private set; } public Container<SelectionBlueprint> SelectionBlueprints { get; private set; }
private SelectionHandler selectionHandler; protected SelectionHandler SelectionHandler { get; private set; }
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; } private IEditorChangeHandler changeHandler { get; set; }
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private EditorClock editorClock { get; set; } private EditorClock editorClock { get; set; }
[Resolved] [Resolved]
private EditorBeatmap beatmap { get; set; } protected EditorBeatmap Beatmap { get; private set; }
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>(); private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
@ -56,22 +56,22 @@ namespace osu.Game.Screens.Edit.Compose.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
selectionHandler = CreateSelectionHandler(); SelectionHandler = CreateSelectionHandler();
selectionHandler.DeselectAll = deselectAll; SelectionHandler.DeselectAll = deselectAll;
AddRangeInternal(new[] AddRangeInternal(new[]
{ {
DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), DragBox = CreateDragBox(selectBlueprintsFromDragRectangle),
selectionHandler, SelectionHandler,
SelectionBlueprints = CreateSelectionBlueprintContainer(), SelectionBlueprints = CreateSelectionBlueprintContainer(),
selectionHandler.CreateProxy(), SelectionHandler.CreateProxy(),
DragBox.CreateProxy().With(p => p.Depth = float.MinValue) DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
}); });
foreach (var obj in beatmap.HitObjects) foreach (var obj in Beatmap.HitObjects)
AddBlueprintFor(obj); AddBlueprintFor(obj);
selectedHitObjects.BindTo(beatmap.SelectedHitObjects); selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
selectedHitObjects.CollectionChanged += (selectedObjects, args) => selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
{ {
switch (args.Action) switch (args.Action)
@ -94,15 +94,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.LoadComplete(); base.LoadComplete();
beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectAdded += AddBlueprintFor;
beatmap.HitObjectRemoved += removeBlueprintFor; Beatmap.HitObjectRemoved += removeBlueprintFor;
} }
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() =>
new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both }; new Container<SelectionBlueprint> { RelativeSizeAxes = Axes.Both };
/// <summary> /// <summary>
/// Creates a <see cref="SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections. /// Creates a <see cref="Components.SelectionHandler"/> which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
/// </summary> /// </summary>
protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler();
@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false; return false;
// store for double-click handling // store for double-click handling
clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); clickedBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
// Deselection should only occur if no selected blueprints are hovered // Deselection should only occur if no selected blueprints are hovered
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false; return false;
// ensure the blueprint which was hovered for the first click is still the hovered blueprint. // ensure the blueprint which was hovered for the first click is still the hovered blueprint.
if (clickedBlueprint == null || selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) if (clickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint)
return false; return false;
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
@ -208,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (DragBox.State == Visibility.Visible) if (DragBox.State == Visibility.Visible)
{ {
DragBox.Hide(); DragBox.Hide();
selectionHandler.UpdateVisibility(); SelectionHandler.UpdateVisibility();
} }
} }
@ -217,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
switch (e.Key) switch (e.Key)
{ {
case Key.Escape: case Key.Escape:
if (!selectionHandler.SelectedBlueprints.Any()) if (!SelectionHandler.SelectedBlueprints.Any())
return false; return false;
deselectAll(); deselectAll();
@ -271,7 +271,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
blueprint.Selected += onBlueprintSelected; blueprint.Selected += onBlueprintSelected;
blueprint.Deselected += onBlueprintDeselected; blueprint.Deselected += onBlueprintDeselected;
if (beatmap.SelectedHitObjects.Contains(hitObject)) if (Beatmap.SelectedHitObjects.Contains(hitObject))
blueprint.Select(); blueprint.Select();
SelectionBlueprints.Add(blueprint); SelectionBlueprints.Add(blueprint);
@ -298,14 +298,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left;
// Todo: This is probably incorrectly disallowing multiple selections on stacked objects // Todo: This is probably incorrectly disallowing multiple selections on stacked objects
if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) if (!allowDeselection && SelectionHandler.SelectedBlueprints.Any(s => s.IsHovered))
return; return;
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren)
{ {
if (blueprint.IsHovered) if (blueprint.IsHovered)
{ {
selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState);
clickSelectionBegan = true; clickSelectionBegan = true;
break; break;
} }
@ -358,23 +358,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void selectAll() private void selectAll()
{ {
SelectionBlueprints.ToList().ForEach(m => m.Select()); SelectionBlueprints.ToList().ForEach(m => m.Select());
selectionHandler.UpdateVisibility(); SelectionHandler.UpdateVisibility();
} }
/// <summary> /// <summary>
/// Deselects all selected <see cref="SelectionBlueprint"/>s. /// Deselects all selected <see cref="SelectionBlueprint"/>s.
/// </summary> /// </summary>
private void deselectAll() => selectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect()); private void deselectAll() => SelectionHandler.SelectedBlueprints.ToList().ForEach(m => m.Deselect());
private void onBlueprintSelected(SelectionBlueprint blueprint) private void onBlueprintSelected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleSelected(blueprint); SelectionHandler.HandleSelected(blueprint);
SelectionBlueprints.ChangeChildDepth(blueprint, 1); SelectionBlueprints.ChangeChildDepth(blueprint, 1);
} }
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleDeselected(blueprint); SelectionHandler.HandleDeselected(blueprint);
SelectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionBlueprints.ChangeChildDepth(blueprint, 0);
} }
@ -391,16 +391,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private void prepareSelectionMovement() private void prepareSelectionMovement()
{ {
if (!selectionHandler.SelectedBlueprints.Any()) if (!SelectionHandler.SelectedBlueprints.Any())
return; return;
// Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement // Any selected blueprint that is hovered can begin the movement of the group, however only the earliest hitobject is used for movement
// A special case is added for when a click selection occurred before the drag // A special case is added for when a click selection occurred before the drag
if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
return; return;
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); movementBlueprint = SelectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
} }
@ -425,14 +425,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
// Move the hitobjects. // Move the hitobjects.
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition)))
return true; return true;
if (result.Time.HasValue) if (result.Time.HasValue)
{ {
// Apply the start time at the newly snapped-to position // Apply the start time at the newly snapped-to position
double offset = result.Time.Value - draggedObject.StartTime; double offset = result.Time.Value - draggedObject.StartTime;
foreach (HitObject obj in selectionHandler.SelectedHitObjects) foreach (HitObject obj in SelectionHandler.SelectedHitObjects)
obj.StartTime += offset; obj.StartTime += offset;
} }
@ -460,10 +460,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (beatmap != null) if (Beatmap != null)
{ {
beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectAdded -= AddBlueprintFor;
beatmap.HitObjectRemoved -= removeBlueprintFor; Beatmap.HitObjectRemoved -= removeBlueprintFor;
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
@ -11,6 +12,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
@ -54,7 +56,37 @@ namespace osu.Game.Screens.Edit.Compose.Components
base.LoadComplete(); base.LoadComplete();
inputManager = GetContainingInputManager(); inputManager = GetContainingInputManager();
Beatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateTogglesFromSelection();
// the updated object may be in the selection
Beatmap.HitObjectUpdated += _ => updateTogglesFromSelection();
NewCombo.ValueChanged += combo =>
{
if (Beatmap.SelectedHitObjects.Count > 0)
{
SelectionHandler.SetNewCombo(combo.NewValue);
} }
else if (currentPlacement != null)
{
// update placement object from toggle
if (currentPlacement.HitObject is IHasComboInformation c)
c.NewCombo = combo.NewValue;
}
};
}
private void updateTogglesFromSelection() =>
NewCombo.Value = Beatmap.SelectedHitObjects.OfType<IHasComboInformation>().All(c => c.NewCombo);
public readonly Bindable<bool> NewCombo = new Bindable<bool> { Description = "New Combo" };
public virtual IEnumerable<Bindable<bool>> Toggles => new[]
{
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
NewCombo
};
#region Placement #region Placement
@ -86,8 +118,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
removePlacement(); removePlacement();
if (currentPlacement != null) if (currentPlacement != null)
{
updatePlacementPosition(); updatePlacementPosition();
} }
}
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
{ {

View File

@ -37,6 +37,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
public IEnumerable<SelectionBlueprint> SelectedBlueprints => selectedBlueprints; public IEnumerable<SelectionBlueprint> SelectedBlueprints => selectedBlueprints;
private readonly List<SelectionBlueprint> selectedBlueprints; private readonly List<SelectionBlueprint> selectedBlueprints;
public int SelectedCount => selectedBlueprints.Count;
public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject);
private Drawable content; private Drawable content;
@ -287,7 +289,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
var comboInfo = h as IHasComboInformation; var comboInfo = h as IHasComboInformation;
if (comboInfo == null) if (comboInfo == null)
throw new InvalidOperationException($"Tried to change combo state of a {h.GetType()}, which doesn't implement {nameof(IHasComboInformation)}"); continue;
comboInfo.NewCombo = state; comboInfo.NewCombo = state;
EditorBeatmap?.UpdateHitObject(h); EditorBeatmap?.UpdateHitObject(h);