From 3c191cfe257280d2ef89983435cb4b3f22fff3a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:08:47 +0900 Subject: [PATCH 1/5] Add basic xmldoc to HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b81e0ce159..0ea57ef4e1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -31,6 +31,11 @@ using osuTK.Input; namespace osu.Game.Rulesets.Edit { + /// + /// Top level container for editor compose mode. + /// Responsible for providing snapping and generally gluing components together. + /// + /// The base type of supported objects. [Cached(Type = typeof(IPlacementHandler))] public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler where TObject : HitObject From bca774a0d4c16b8ca53ddd5dc7d19061dbf35971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:09:31 +0900 Subject: [PATCH 2/5] Allow BlueprintContainer to specify toggles --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e1cbfa93f6..2127385ab5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" }; - protected override IEnumerable Toggles => new[] + protected override IEnumerable> Toggles => base.Toggles.Concat(new[] { distanceSnapToggle - }; + }); private BindableList selectedHitObjects; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0ea57ef4e1..b2d7c40a22 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Edit /// A collection of toggles which will be displayed to the user. /// The display name will be decided by . /// - protected virtual IEnumerable Toggles => Enumerable.Empty(); + protected virtual IEnumerable> Toggles => BlueprintContainer.Toggles; /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. From e009264f1029fa336439f2a2d10d081b6b2d9c03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:10:30 +0900 Subject: [PATCH 3/5] Add new combo toggle to main composer interface --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 18 ++++---- .../Components/ComposeBlueprintContainer.cs | 41 +++++++++++++++++++ .../Compose/Components/SelectionHandler.cs | 2 + 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 02d5955ae6..d986b71380 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit /// /// The that is being placed. /// - protected readonly HitObject HitObject; + public readonly HitObject HitObject; [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index bf1e18771f..aa567dbdf4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private EditorClock editorClock { get; set; } [Resolved] - private EditorBeatmap beatmap { get; set; } + protected EditorBeatmap Beatmap { get; private set; } private readonly BindableList selectedHitObjects = new BindableList(); @@ -68,10 +68,10 @@ namespace osu.Game.Screens.Edit.Compose.Components DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); - foreach (var obj in beatmap.HitObjects) + foreach (var obj in Beatmap.HitObjects) AddBlueprintFor(obj); - selectedHitObjects.BindTo(beatmap.SelectedHitObjects); + selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); selectedHitObjects.CollectionChanged += (selectedObjects, args) => { switch (args.Action) @@ -94,8 +94,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.LoadComplete(); - beatmap.HitObjectAdded += AddBlueprintFor; - beatmap.HitObjectRemoved += removeBlueprintFor; + Beatmap.HitObjectAdded += AddBlueprintFor; + Beatmap.HitObjectRemoved += removeBlueprintFor; } protected virtual Container CreateSelectionBlueprintContainer() => @@ -271,7 +271,7 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Selected += onBlueprintSelected; blueprint.Deselected += onBlueprintDeselected; - if (beatmap.SelectedHitObjects.Contains(hitObject)) + if (Beatmap.SelectedHitObjects.Contains(hitObject)) blueprint.Select(); SelectionBlueprints.Add(blueprint); @@ -460,10 +460,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Dispose(isDisposing); - if (beatmap != null) + if (Beatmap != null) { - beatmap.HitObjectAdded -= AddBlueprintFor; - beatmap.HitObjectRemoved -= removeBlueprintFor; + Beatmap.HitObjectAdded -= AddBlueprintFor; + Beatmap.HitObjectRemoved -= removeBlueprintFor; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index e1f311f1b8..2a7c52579f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -11,6 +12,7 @@ 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 osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -54,8 +56,45 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); 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) + { + foreach (var h in Beatmap.SelectedHitObjects) + { + if (h is IHasComboInformation c) + { + c.NewCombo = combo.NewValue; + Beatmap.UpdateHitObject(h); + } + } + } + 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().All(c => c.NewCombo); + + public readonly Bindable NewCombo = new Bindable { Description = "New Combo" }; + + public virtual IEnumerable> Toggles => new[] + { + //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. + NewCombo + }; + #region Placement /// @@ -86,7 +125,9 @@ namespace osu.Game.Screens.Edit.Compose.Components removePlacement(); if (currentPlacement != null) + { updatePlacementPosition(); + } } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 6e2c8bd01c..ca22b443fb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; + public int SelectedCount => selectedBlueprints.Count; + public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject); private Drawable content; From a6adf8334eea25818c0c38ba7dc59ff7358ee910 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 14:19:35 +0900 Subject: [PATCH 4/5] Use existing method to update combo state of selection --- .../Compose/Components/BlueprintContainer.cs | 44 +++++++++---------- .../Components/ComposeBlueprintContainer.cs | 9 +--- .../Compose/Components/SelectionHandler.cs | 2 +- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index aa567dbdf4..fc37aa577c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { /// /// A container which provides a "blueprint" display of hitobjects. - /// Includes selection and manipulation support via a . + /// Includes selection and manipulation support via a . /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected Container SelectionBlueprints { get; private set; } - private SelectionHandler selectionHandler; + protected SelectionHandler SelectionHandler; [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } @@ -56,15 +56,15 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - selectionHandler = CreateSelectionHandler(); - selectionHandler.DeselectAll = deselectAll; + SelectionHandler = CreateSelectionHandler(); + SelectionHandler.DeselectAll = deselectAll; AddRangeInternal(new[] { DragBox = CreateDragBox(selectBlueprintsFromDragRectangle), - selectionHandler, + SelectionHandler, SelectionBlueprints = CreateSelectionBlueprintContainer(), - selectionHandler.CreateProxy(), + SelectionHandler.CreateProxy(), DragBox.CreateProxy().With(p => p.Depth = float.MinValue) }); @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components new Container { RelativeSizeAxes = Axes.Both }; /// - /// Creates a which outlines s and handles movement of selections. + /// Creates a which outlines s and handles movement of selections. /// protected virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Compose.Components return false; // 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 // 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; // 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; editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); @@ -208,7 +208,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (DragBox.State == Visibility.Visible) { DragBox.Hide(); - selectionHandler.UpdateVisibility(); + SelectionHandler.UpdateVisibility(); } } @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.Escape: - if (!selectionHandler.SelectedBlueprints.Any()) + if (!SelectionHandler.SelectedBlueprints.Any()) return false; deselectAll(); @@ -298,14 +298,14 @@ namespace osu.Game.Screens.Edit.Compose.Components bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; // 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; foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); clickSelectionBegan = true; break; } @@ -358,23 +358,23 @@ namespace osu.Game.Screens.Edit.Compose.Components private void selectAll() { SelectionBlueprints.ToList().ForEach(m => m.Select()); - selectionHandler.UpdateVisibility(); + SelectionHandler.UpdateVisibility(); } /// /// Deselects all selected s. /// - 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) { - selectionHandler.HandleSelected(blueprint); + SelectionHandler.HandleSelected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 1); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { - selectionHandler.HandleDeselected(blueprint); + SelectionHandler.HandleDeselected(blueprint); SelectionBlueprints.ChangeChildDepth(blueprint, 0); } @@ -391,16 +391,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void prepareSelectionMovement() { - if (!selectionHandler.SelectedBlueprints.Any()) + if (!SelectionHandler.SelectedBlueprints.Any()) return; // 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 - if (!clickSelectionBegan && !selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) return; // 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 } @@ -425,14 +425,14 @@ namespace osu.Game.Screens.Edit.Compose.Components var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) + if (!SelectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) return true; if (result.Time.HasValue) { // Apply the start time at the newly snapped-to position double offset = result.Time.Value - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) + foreach (HitObject obj in SelectionHandler.SelectedHitObjects) obj.StartTime += offset; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 2a7c52579f..6f66c1bd6f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -66,14 +66,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (Beatmap.SelectedHitObjects.Count > 0) { - foreach (var h in Beatmap.SelectedHitObjects) - { - if (h is IHasComboInformation c) - { - c.NewCombo = combo.NewValue; - Beatmap.UpdateHitObject(h); - } - } + SelectionHandler.SetNewCombo(combo.NewValue); } else if (currentPlacement != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index ca22b443fb..1c5c3179ca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -285,7 +285,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var comboInfo = h as IHasComboInformation; 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; EditorBeatmap?.UpdateHitObject(h); From 51cc644b7b203110f88b7bee33cd6321ab1fb66b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 17:42:49 +0900 Subject: [PATCH 5/5] Fix set access to SelectionHandler Co-authored-by: Dan Balasescu --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8934a1b6d3..8908520cd7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public Container SelectionBlueprints { get; private set; } - protected SelectionHandler SelectionHandler; + protected SelectionHandler SelectionHandler { get; private set; } [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; }