From 6314694557e28d60b5bde1f90d2ce6ef9004c570 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:13:45 +0900 Subject: [PATCH 01/44] Make HitObjectMaskLayer always create masks for all objects --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++----- .../Compose/Layers/HitObjectMaskLayer.cs | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c076b53f51..93a8980aa7 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit return; } - HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(this); + HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(rulesetContainer.Playfield, this); SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); var layerBelowRuleset = new BorderLayer @@ -122,11 +122,6 @@ namespace osu.Game.Rulesets.Edit } }; - selectionLayer.ObjectSelected += hitObjectMaskLayer.AddOverlay; - selectionLayer.ObjectDeselected += hitObjectMaskLayer.RemoveOverlay; - selectionLayer.SelectionCleared += hitObjectMaskLayer.RemoveSelectionOverlay; - selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay; - toolboxCollection.Items = new[] { new RadioButton("Select", () => setCompositionTool(null)) } .Concat( @@ -267,7 +262,7 @@ namespace osu.Game.Rulesets.Edit /// and handles all hitobject movement/pattern adjustments. /// /// The overlays. - public virtual SelectionBox CreateSelectionOverlay(IReadOnlyList overlays) => new SelectionBox(overlays); + public virtual SelectionBox CreateSelectionBox(IReadOnlyList overlays) => new SelectionBox(overlays); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 46b09e2c23..1412d98f31 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -2,31 +2,43 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public class HitObjectMaskLayer : CompositeDrawable { + private readonly Playfield playfield; private readonly HitObjectComposer composer; private readonly Container overlayContainer; - public HitObjectMaskLayer(HitObjectComposer composer) + public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { + this.playfield = playfield; this.composer = composer; + RelativeSizeAxes = Axes.Both; InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both }; } + [BackgroundDependencyLoader] + private void load() + { + foreach (var obj in playfield.HitObjects.Objects) + addOverlay(obj); + } + /// /// Adds an overlay for a which adds movement support. /// /// The to create an overlay for. - public void AddOverlay(DrawableHitObject hitObject) + private void addOverlay(DrawableHitObject hitObject) { var overlay = composer.CreateMaskFor(hitObject); if (overlay == null) @@ -39,7 +51,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Removes the overlay for a . /// /// The to remove the overlay for. - public void RemoveOverlay(DrawableHitObject hitObject) + private void removeOverlay(DrawableHitObject hitObject) { var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject); if (existing == null) @@ -51,13 +63,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private SelectionBox currentSelectionBox; - public void AddSelectionOverlay() + private void addSelectionBox() { if (overlayContainer.Count > 0) - AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer)); + AddInternal(currentSelectionBox = composer.CreateSelectionBox(overlayContainer)); } - public void RemoveSelectionOverlay() + private void removeSelectionBox() { currentSelectionBox?.Hide(); currentSelectionBox?.Expire(); From 4bdfc9dca9c288304129ae0e3b64c7bf000a7aa5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:19:14 +0900 Subject: [PATCH 02/44] Fix testcase --- osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index bbbfef477a..d56417f144 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; @@ -32,7 +33,8 @@ namespace osu.Game.Tests.Visual typeof(HitObjectMask), typeof(HitCircleMask), typeof(SliderMask), - typeof(SliderCircleMask) + typeof(SliderCircleMask), + typeof(NotNullAttribute) }; private DependencyContainer dependencies; From 57e4281601feb42d49c851efb5ea93d50c2ef455 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 17:19:24 +0900 Subject: [PATCH 03/44] Make HitObjectMasks VisibilityContainers --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 051b42fec6..e79e540bce 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -9,13 +9,17 @@ namespace osu.Game.Rulesets.Edit /// /// A mask placed above a adding editing functionality. /// - public class HitObjectMask : Container + public class HitObjectMask : VisibilityContainer { public readonly DrawableHitObject HitObject; public HitObjectMask(DrawableHitObject hitObject) { HitObject = hitObject; + State = Visibility.Hidden; } + + protected override void PopIn() => Alpha = 1; + protected override void PopOut() => Alpha = 0; } } From 6d4f94756e339f1c06d11f3ba077b6aae006394d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:06:45 +0900 Subject: [PATCH 04/44] Rewrite the way drag + click selections happen The general idea here is that we need the masks to handle mouse down events, as they need to handle the drag (mousedown -> drag immediately). I've rewritten the editor selections to use events, as there are some 3 different components that handle/trigger selections in different ways. 1. All selections/deselections now propagate through `HitObjectMask.Select()`/`HitObjectMask.Deselect()`. 2. Components that react to changes in the selection bind to the masks' `Selected`/`Deselected` events, and track them/change their states locally. 3. Masks provide a `SingleSelectionRequested` event which is invoked on the mouse-down event. Various components bind to this event to perform state changes locally in this scenario. 4. `DragBox` now handles all drag input locally. It triggers `Select`/`Deselect` on the masks it needs to. 5. `SelectionBox` handles the display of itself locally. 6. `SelectionBox` handles movement of groups of masks locally. 7. `HitObjectMasks` handles movement of itself locally. --- .../Selection/Overlays/HitCircleMask.cs | 2 + .../Layers/Selection/Overlays/SliderMask.cs | 4 + .../Objects/Drawables/DrawableSlider.cs | 4 - .../Visual/TestCaseEditorSelectionLayer.cs | 1 - osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 80 ++++++ .../Objects/Drawables/DrawableHitObject.cs | 12 - .../Edit/Screens/Compose/Layers/DragBox.cs | 97 +++++++ .../Compose/Layers/HitObjectMaskLayer.cs | 54 +++- .../Screens/Compose/Layers/SelectionBox.cs | 157 ++++++++---- .../Screens/Compose/Layers/SelectionLayer.cs | 240 ------------------ 11 files changed, 334 insertions(+), 331 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs delete mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs index b48dd73bb5..89a7686581 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays Size = hitCircle.Size; Scale = hitCircle.Scale; + CornerRadius = Size.X / 2; + AddInternal(new RingPiece()); hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs index 53f02617cd..629bce1847 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -59,5 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos); + + public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); + public override Quad SelectionQuad => body.PathDrawQuad; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 3872821b96..5373926138 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -10,7 +10,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; -using osu.Framework.Graphics.Primitives; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using OpenTK.Graphics; @@ -177,8 +176,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ApproachCircle; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); - - public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition); - public override Quad SelectionQuad => Body.PathDrawQuad; } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index d56417f144..4e39548b5b 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -25,7 +25,6 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionLayer), typeof(SelectionBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 93a8980aa7..1a587bf8f5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -65,9 +65,6 @@ namespace osu.Game.Rulesets.Edit return; } - HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(rulesetContainer.Playfield, this); - SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); - var layerBelowRuleset = new BorderLayer { RelativeSizeAxes = Axes.Both, @@ -75,12 +72,7 @@ namespace osu.Game.Rulesets.Edit }; var layerAboveRuleset = CreateLayerContainer(); - layerAboveRuleset.Children = new Drawable[] - { - selectionLayer, // Below object overlays for input - hitObjectMaskLayer, - selectionLayer.CreateProxy() // Proxy above object overlays for selections - }; + layerAboveRuleset.Child = new HitObjectMaskLayer(rulesetContainer.Playfield, this); layerContainers.Add(layerBelowRuleset); layerContainers.Add(layerAboveRuleset); @@ -259,10 +251,10 @@ namespace osu.Game.Rulesets.Edit /// /// Creates a which outlines s - /// and handles all hitobject movement/pattern adjustments. + /// and handles hitobject pattern adjustments. /// /// The overlays. - public virtual SelectionBox CreateSelectionBox(IReadOnlyList overlays) => new SelectionBox(overlays); + public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index e79e540bce..44ee981783 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -1,8 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit.Types; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Edit { @@ -11,15 +16,90 @@ namespace osu.Game.Rulesets.Edit /// public class HitObjectMask : VisibilityContainer { + public event Action Selected; + public event Action Deselected; + public event Action SingleSelectionRequested; + public readonly DrawableHitObject HitObject; + protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; + public override bool HandleMouseInput => true; + public HitObjectMask(DrawableHitObject hitObject) { HitObject = hitObject; + + AlwaysPresent = true; State = Visibility.Hidden; } + /// + /// Selects this , causing it to become visible. + /// + /// True if the was selected. False if the was already selected. + public bool Select() + { + if (State == Visibility.Visible) + return false; + + Show(); + Selected?.Invoke(this); + return true; + } + + /// + /// Deselects this , causing it to become invisible. + /// + /// True if the was deselected. False if the was already deselected. + public bool Deselect() + { + if (State == Visibility.Hidden) + return false; + + Hide(); + Deselected?.Invoke(this); + return true; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + if (HitObject.IsPresent) + { + SingleSelectionRequested?.Invoke(this); + Select(); + return true; + } + + return false; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + // Todo: Various forms of snapping + switch (HitObject.HitObject) + { + case IHasEditablePosition editablePosition: + editablePosition.OffsetPosition(state.Mouse.Delta); + break; + } + return true; + } + + protected override bool OnDragEnd(InputState state) => true; + protected override void PopIn() => Alpha = 1; protected override void PopOut() => Alpha = 0; + + /// + /// The screen-space point that causes this to be selected. + /// + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + + /// + /// The screen-space quad that outlines this for selections. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 348364a2bf..fdfef14a88 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -6,14 +6,12 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; -using OpenTK; using OpenTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -231,16 +229,6 @@ namespace osu.Game.Rulesets.Objects.Drawables protected virtual void CheckForJudgements(bool userTriggered, double timeOffset) { } - - /// - /// The screen-space point that causes this to be selected in the Editor. - /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; - - /// - /// The screen-space quad that outlines this for selections in the Editor. - /// - public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs new file mode 100644 index 0000000000..f70696f0f1 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Rulesets.Edit; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + /// + /// A box that represents a drag selection. + /// + public class DragBox : CompositeDrawable + { + public event Action DragEnd; + + private readonly IEnumerable hitObjectMasks; + + private Drawable box; + + /// + /// Creates a new . + /// + /// The selectable s. + public DragBox(IEnumerable hitObjectMasks) + { + this.hitObjectMasks = hitObjectMasks; + + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = box = new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = SelectionBox.BORDER_RADIUS, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f + } + }; + } + + protected override bool OnDragStart(InputState state) + { + this.FadeIn(250, Easing.OutQuint); + return true; + } + + protected override bool OnDrag(InputState state) + { + var dragPosition = state.Mouse.NativeState.Position; + var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; + + var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); + + // We use AABBFloat instead of RectangleF since it handles negative sizes for us + SetDragRectangle(dragQuad.AABBFloat); + + return true; + } + + protected override bool OnDragEnd(InputState state) + { + this.FadeOut(250, Easing.OutQuint); + DragEnd?.Invoke(); + return true; + } + + public void SetDragRectangle(RectangleF screenSpaceRectangle) + { + var topLeft = ToLocalSpace(screenSpaceRectangle.TopLeft); + var bottomRight = ToLocalSpace(screenSpaceRectangle.BottomRight); + + box.Position = topLeft; + box.Size = bottomRight - topLeft; + + foreach (var mask in hitObjectMasks) + { + if (mask.IsAlive && mask.IsPresent && screenSpaceRectangle.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 1412d98f31..ac7ba76220 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -17,6 +19,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private readonly Container overlayContainer; + private readonly SelectionBox selectionBox; + + private readonly HashSet selectedObjects = new HashSet(); + public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { this.playfield = playfield; @@ -24,7 +30,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers RelativeSizeAxes = Axes.Both; - InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both }; + overlayContainer = new Container(); + selectionBox = composer.CreateSelectionBox(); + + var dragBox = new DragBox(overlayContainer); + dragBox.DragEnd += () => selectionBox.FinishSelection(); + + InternalChildren = new Drawable[] + { + dragBox, + overlayContainer, + selectionBox, + dragBox.CreateProxy() + }; } [BackgroundDependencyLoader] @@ -44,7 +62,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (overlay == null) return; + overlay.Selected += onSelected; + overlay.Deselected += onDeselected; + overlay.SingleSelectionRequested += onSingleSelectionRequested; + overlayContainer.Add(overlay); + selectionBox.AddMask(overlay); } /// @@ -57,22 +80,29 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (existing == null) return; - existing.Hide(); - existing.Expire(); + existing.Selected -= onSelected; + existing.Deselected -= onDeselected; + existing.SingleSelectionRequested -= onSingleSelectionRequested; + + overlayContainer.Remove(existing); + selectionBox.RemoveMask(existing); } - private SelectionBox currentSelectionBox; + private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); - private void addSelectionBox() + private void onDeselected(HitObjectMask mask) => selectedObjects.Remove(mask); + + private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (overlayContainer.Count > 0) - AddInternal(currentSelectionBox = composer.CreateSelectionBox(overlayContainer)); + DeselectAll(); + return true; } - private void removeSelectionBox() - { - currentSelectionBox?.Hide(); - currentSelectionBox?.Expire(); - } + /// + /// Deselects all selected s. + /// + public void DeselectAll() => overlayContainer.ToList().ForEach(m => m.Deselect()); } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index 0e5d824559..8249c08a7a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -19,84 +20,138 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// A box which surrounds s and provides interactive handles, context menus etc. /// - public class SelectionBox : VisibilityContainer + public class SelectionBox : CompositeDrawable { - private readonly IReadOnlyList overlays; - public const float BORDER_RADIUS = 2; - public SelectionBox(IReadOnlyList overlays) + private readonly HashSet selectedMasks = new HashSet(); + + private Drawable box; + + public SelectionBox() { - this.overlays = overlays; - - Masking = true; - BorderThickness = BORDER_RADIUS; - - InternalChild = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - }; - - State = Visibility.Visible; + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Alpha = 0; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BorderColour = colours.Yellow; + InternalChild = box = new Container + { + Masking = true, + BorderThickness = BORDER_RADIUS, + BorderColour = colours.Yellow, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + } + }; + } + + public void AddMask(HitObjectMask mask) + { + mask.Selected += onSelected; + mask.Deselected += onDeselected; + mask.SingleSelectionRequested += onSingleSelectionRequested; + } + + public void RemoveMask(HitObjectMask mask) + { + mask.Selected -= onSelected; + mask.Deselected -= onDeselected; + mask.SingleSelectionRequested -= onSingleSelectionRequested; + } + + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + private void onDeselected(HitObjectMask mask) + { + selectedMasks.Remove(mask); + + if (selectedMasks.Count == 0) + FinishSelection(); + } + + private void onSingleSelectionRequested(HitObjectMask mask) + { + selectedMasks.Add(mask); + FinishSelection(); + } + + // Only handle clicks on the selected masks + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnClick(InputState state) + { + if (state.Mouse.NativeState.PositionMouseDown == null) + throw new InvalidOperationException("Click event received without a mouse down position."); + + // If the mouse has moved slightly, but hasn't been dragged, select the mask which would've handled the mouse down + selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); + return true; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + // Todo: Various forms of snapping + + foreach (var mask in selectedMasks) + { + switch (mask.HitObject) + { + case IHasEditablePosition editablePosition: + editablePosition.OffsetPosition(state.Mouse.Delta); + break; + } + } + + return true; + } + + protected override bool OnDragEnd(InputState state) => true; + + public void FinishSelection() + { + if (selectedMasks.Count > 0) + Show(); + else + Hide(); } protected override void Update() { base.Update(); + if (selectedMasks.Count == 0) + return; + // Todo: We might need to optimise this // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - foreach (var obj in overlays) + bool hasSelection = false; + + foreach (var mask in selectedMasks) { - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.BottomRight)); + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight)); } topLeft -= new Vector2(5); bottomRight += new Vector2(5); - Size = bottomRight - topLeft; - Position = topLeft; + box.Size = bottomRight - topLeft; + box.Position = topLeft; } - - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => overlays.Any(o => o.ReceiveMouseInputAt(screenSpacePos)); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - // Todo: Various forms of snapping - foreach (var hitObject in overlays.Select(o => o.HitObject.HitObject)) - { - switch (hitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } - } - return true; - } - - protected override bool OnDragEnd(InputState state) => true; - - public override bool DisposeOnDeathRemoval => true; - - protected override void PopIn() => this.FadeIn(); - protected override void PopOut() => this.FadeOut(); } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs deleted file mode 100644 index ab51385980..0000000000 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using OpenTK; -using OpenTK.Graphics; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Screens.Edit.Screens.Compose.Layers -{ - public class SelectionLayer : CompositeDrawable - { - /// - /// Invoked when a is selected. - /// - public event Action ObjectSelected; - - /// - /// Invoked when a is deselected. - /// - public event Action ObjectDeselected; - - /// - /// Invoked when the selection has been cleared. - /// - public event Action SelectionCleared; - - /// - /// Invoked when the user has finished selecting all s. - /// - public event Action SelectionFinished; - - private readonly Playfield playfield; - - public SelectionLayer(Playfield playfield) - { - this.playfield = playfield; - - RelativeSizeAxes = Axes.Both; - } - - private DragBox dragBox; - - private readonly HashSet selectedHitObjects = new HashSet(); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - DeselectAll(); - return true; - } - - protected override bool OnDragStart(InputState state) - { - AddInternal(dragBox = new DragBox()); - return true; - } - - protected override bool OnDrag(InputState state) - { - dragBox.Show(); - - var dragPosition = state.Mouse.NativeState.Position; - var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; - - var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - - dragBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); - selectQuad(screenSpaceDragQuad); - - return true; - } - - protected override bool OnDragEnd(InputState state) - { - dragBox.Hide(); - dragBox.Expire(); - - finishSelection(); - - return true; - } - - protected override bool OnClick(InputState state) - { - selectPoint(state.Mouse.NativeState.Position); - finishSelection(); - - return true; - } - - /// - /// Selects a . - /// - /// The to select. - public void Select(DrawableHitObject hitObject) - { - if (!select(hitObject)) - return; - - clearSelection(); - finishSelection(); - } - - /// - /// Selects a without performing capture updates. - /// - /// The to select. - /// Whether was selected. - private bool select(DrawableHitObject hitObject) - { - if (!selectedHitObjects.Add(hitObject)) - return false; - - ObjectSelected?.Invoke(hitObject); - return true; - } - - /// - /// Deselects a . - /// - /// The to deselect. - public void Deselect(DrawableHitObject hitObject) - { - if (!deselect(hitObject)) - return; - - clearSelection(); - finishSelection(); - } - - /// - /// Deselects a without performing capture updates. - /// - /// The to deselect. - /// Whether the was deselected. - private bool deselect(DrawableHitObject hitObject) - { - if (!selectedHitObjects.Remove(hitObject)) - return false; - - ObjectDeselected?.Invoke(hitObject); - return true; - } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() - { - selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h)); - selectedHitObjects.Clear(); - - clearSelection(); - } - - /// - /// Selects all hitobjects that are present within the area of a . - /// - /// The selection . - // Todo: If needed we can severely reduce allocations in this method - private void selectQuad(Quad screenSpaceQuad) - { - var expectedSelection = playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint)).ToList(); - - var toRemove = selectedHitObjects.Except(expectedSelection).ToList(); - foreach (var obj in toRemove) - deselect(obj); - - expectedSelection.ForEach(h => select(h)); - } - - /// - /// Selects the top-most hitobject that is present under a specific point. - /// - /// The to select at. - private void selectPoint(Vector2 screenSpacePoint) - { - var target = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); - if (target == null) - return; - - select(target); - } - - private void clearSelection() => SelectionCleared?.Invoke(); - - private void finishSelection() - { - if (selectedHitObjects.Count == 0) - return; - SelectionFinished?.Invoke(); - } - - /// - /// A box that represents a drag selection. - /// - private class DragBox : VisibilityContainer - { - /// - /// Creates a new . - /// - public DragBox() - { - Masking = true; - BorderColour = Color4.White; - BorderThickness = SelectionBox.BORDER_RADIUS; - - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - }; - } - - public void SetDragRectangle(RectangleF rectangle) - { - var topLeft = Parent.ToLocalSpace(rectangle.TopLeft); - var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight); - - Position = topLeft; - Size = bottomRight - topLeft; - } - - public override bool DisposeOnDeathRemoval => true; - - protected override void PopIn() => this.FadeIn(250, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(250, Easing.OutQuint); - } - } -} From 04874bcda44092241f53a301d0fc8c85eefd02f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:09:22 +0900 Subject: [PATCH 05/44] "overlay" -> "mask" --- .../Compose/Layers/HitObjectMaskLayer.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ac7ba76220..6370456053 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { private readonly Playfield playfield; private readonly HitObjectComposer composer; - private readonly Container overlayContainer; + private readonly Container maskContainer; private readonly SelectionBox selectionBox; @@ -30,16 +30,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers RelativeSizeAxes = Axes.Both; - overlayContainer = new Container(); + maskContainer = new Container(); selectionBox = composer.CreateSelectionBox(); - var dragBox = new DragBox(overlayContainer); + var dragBox = new DragBox(maskContainer); dragBox.DragEnd += () => selectionBox.FinishSelection(); InternalChildren = new Drawable[] { dragBox, - overlayContainer, + maskContainer, selectionBox, dragBox.CreateProxy() }; @@ -49,43 +49,43 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void load() { foreach (var obj in playfield.HitObjects.Objects) - addOverlay(obj); + addMask(obj); } /// - /// Adds an overlay for a which adds movement support. + /// Adds a mask for a which adds movement support. /// - /// The to create an overlay for. - private void addOverlay(DrawableHitObject hitObject) + /// The to create a mask for. + private void addMask(DrawableHitObject hitObject) { - var overlay = composer.CreateMaskFor(hitObject); - if (overlay == null) + var mask = composer.CreateMaskFor(hitObject); + if (mask == null) return; - overlay.Selected += onSelected; - overlay.Deselected += onDeselected; - overlay.SingleSelectionRequested += onSingleSelectionRequested; + mask.Selected += onSelected; + mask.Deselected += onDeselected; + mask.SingleSelectionRequested += onSingleSelectionRequested; - overlayContainer.Add(overlay); - selectionBox.AddMask(overlay); + maskContainer.Add(mask); + selectionBox.AddMask(mask); } /// - /// Removes the overlay for a . + /// Removes the mask for a . /// - /// The to remove the overlay for. - private void removeOverlay(DrawableHitObject hitObject) + /// The to remove the mask for. + private void removeMask(DrawableHitObject hitObject) { - var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject); - if (existing == null) + var mask = maskContainer.FirstOrDefault(h => h.HitObject == hitObject); + if (mask == null) return; - existing.Selected -= onSelected; - existing.Deselected -= onDeselected; - existing.SingleSelectionRequested -= onSingleSelectionRequested; + mask.Selected -= onSelected; + mask.Deselected -= onDeselected; + mask.SingleSelectionRequested -= onSingleSelectionRequested; - overlayContainer.Remove(existing); - selectionBox.RemoveMask(existing); + maskContainer.Remove(mask); + selectionBox.RemoveMask(mask); } private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); @@ -103,6 +103,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => overlayContainer.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => maskContainer.ToList().ForEach(m => m.Deselect()); } } From 346de77776f37038f217bbb742b56cddc428684e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:13:28 +0900 Subject: [PATCH 06/44] Cleanup DragBox --- .../Edit/Screens/Compose/Layers/DragBox.cs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index f70696f0f1..f28f47ba48 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -12,10 +12,13 @@ using OpenTK.Graphics; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box that represents a drag selection. + /// A box that handles and displays drag selection for a collection of s. /// public class DragBox : CompositeDrawable { + /// + /// Invoked when the drag selection has finished. + /// public event Action DragEnd; private readonly IEnumerable hitObjectMasks; @@ -65,7 +68,21 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); // We use AABBFloat instead of RectangleF since it handles negative sizes for us - SetDragRectangle(dragQuad.AABBFloat); + var dragRectangle = dragQuad.AABBFloat; + + var topLeft = ToLocalSpace(dragRectangle.TopLeft); + var bottomRight = ToLocalSpace(dragRectangle.BottomRight); + + box.Position = topLeft; + box.Size = bottomRight - topLeft; + + foreach (var mask in hitObjectMasks) + { + if (mask.IsAlive && mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } return true; } @@ -76,22 +93,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers DragEnd?.Invoke(); return true; } - - public void SetDragRectangle(RectangleF screenSpaceRectangle) - { - var topLeft = ToLocalSpace(screenSpaceRectangle.TopLeft); - var bottomRight = ToLocalSpace(screenSpaceRectangle.BottomRight); - - box.Position = topLeft; - box.Size = bottomRight - topLeft; - - foreach (var mask in hitObjectMasks) - { - if (mask.IsAlive && mask.IsPresent && screenSpaceRectangle.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - } } } From 1018711cc90ebfd0a8d4aac3907328274d4b32fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:20:56 +0900 Subject: [PATCH 07/44] Cleanup SelectionBox --- .../Compose/Layers/HitObjectMaskLayer.cs | 2 +- .../Screens/Compose/Layers/SelectionBox.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 6370456053..7b140ac37b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(); var dragBox = new DragBox(maskContainer); - dragBox.DragEnd += () => selectionBox.FinishSelection(); + dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index 8249c08a7a..bfdcdb0456 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -12,13 +12,12 @@ using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; -using osu.Game.Rulesets.Objects.Drawables; using OpenTK; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box which surrounds s and provides interactive handles, context menus etc. + /// A box which surrounds s and provides interactive handles, context menus etc. /// public class SelectionBox : CompositeDrawable { @@ -52,6 +51,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers }; } + /// + /// Tracks a selectable . + /// + /// The to track. public void AddMask(HitObjectMask mask) { mask.Selected += onSelected; @@ -59,6 +62,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers mask.SingleSelectionRequested += onSingleSelectionRequested; } + /// + /// Stops tracking a . + /// + /// The to stop tracking. public void RemoveMask(HitObjectMask mask) { mask.Selected -= onSelected; @@ -72,17 +79,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { selectedMasks.Remove(mask); + // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection if (selectedMasks.Count == 0) - FinishSelection(); + UpdateVisibility(); } private void onSingleSelectionRequested(HitObjectMask mask) { selectedMasks.Add(mask); - FinishSelection(); + UpdateVisibility(); } - // Only handle clicks on the selected masks + // Only handle input on the selected masks public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; @@ -92,7 +100,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (state.Mouse.NativeState.PositionMouseDown == null) throw new InvalidOperationException("Click event received without a mouse down position."); - // If the mouse has moved slightly, but hasn't been dragged, select the mask which would've handled the mouse down + // We handled mousedown, but if the mouse has been clicked and not dragged, select the mask which would've handled the mouse down + // A mousedown event is triggered such that a single selection is requested selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); return true; } @@ -118,7 +127,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnDragEnd(InputState state) => true; - public void FinishSelection() + /// + /// Updates whether this is visible. + /// + public void UpdateVisibility() { if (selectedMasks.Count > 0) Show(); From d8f26f22609015ffde96cd57244e91c9998c0a7c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:22:42 +0900 Subject: [PATCH 08/44] Make HitObjectMaskLayer not iterate through all masks when deselecting --- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 7b140ac37b..2907f48568 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly SelectionBox selectionBox; - private readonly HashSet selectedObjects = new HashSet(); + private readonly HashSet selectedMasks = new HashSet(); public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -88,9 +88,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox.RemoveMask(mask); } - private void onSelected(HitObjectMask mask) => selectedObjects.Add(mask); + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - private void onDeselected(HitObjectMask mask) => selectedObjects.Remove(mask); + private void onDeselected(HitObjectMask mask) => selectedMasks.Remove(mask); private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); @@ -103,6 +103,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => maskContainer.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); } } From 4446aeaa0de1b4f92e2644d3065343042581741b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:27:14 +0900 Subject: [PATCH 09/44] Commenting + cleanup of HitObjectMask/HitObjectMaskLayer --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 28 +++++++++++++------ .../Compose/Layers/HitObjectMaskLayer.cs | 12 ++++---- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 44ee981783..8b0d40dadc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -16,14 +16,29 @@ namespace osu.Game.Rulesets.Edit /// public class HitObjectMask : VisibilityContainer { + /// + /// Invoked when this has been selected. + /// public event Action Selected; + + /// + /// Invoked when this has been deselected. + /// public event Action Deselected; + + /// + /// Invoked when this is requesting to be the single selection. + /// This has not been selected at this point, but will be selected immediately afterwards. + /// public event Action SingleSelectionRequested; + /// + /// The which this applies to. + /// public readonly DrawableHitObject HitObject; protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; - public override bool HandleMouseInput => true; + public override bool HandleMouseInput => HitObject.IsPresent; public HitObjectMask(DrawableHitObject hitObject) { @@ -63,14 +78,9 @@ namespace osu.Game.Rulesets.Edit protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - if (HitObject.IsPresent) - { - SingleSelectionRequested?.Invoke(this); - Select(); - return true; - } - - return false; + SingleSelectionRequested?.Invoke(this); + Select(); + return true; } protected override bool OnDragStart(InputState state) => true; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 2907f48568..ca161a6f37 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -17,9 +17,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { private readonly Playfield playfield; private readonly HitObjectComposer composer; - private readonly Container maskContainer; - private readonly SelectionBox selectionBox; + private Container maskContainer; + private SelectionBox selectionBox; private readonly HashSet selectedMasks = new HashSet(); @@ -29,7 +29,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers this.composer = composer; RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] + private void load() + { maskContainer = new Container(); selectionBox = composer.CreateSelectionBox(); @@ -43,11 +47,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox, dragBox.CreateProxy() }; - } - [BackgroundDependencyLoader] - private void load() - { foreach (var obj in playfield.HitObjects.Objects) addMask(obj); } From d9c5a0c6d1e7af52da3393ca2c90344e2eba416e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:38:44 +0900 Subject: [PATCH 10/44] Fix position editing not working --- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 3 ++- osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index c00c30ced9..f64db6ba9e 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,10 +7,11 @@ using osu.Game.Rulesets.Objects; using OpenTK; using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Types; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition { public const double OBJECT_RADIUS = 64; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index bfdcdb0456..ad8c846bbf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers foreach (var mask in selectedMasks) { - switch (mask.HitObject) + switch (mask.HitObject.HitObject) { case IHasEditablePosition editablePosition: editablePosition.OffsetPosition(state.Mouse.Delta); From 3129c2cc75fd82e2a324aaf29d8e7f0d6cb771fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:41:49 +0900 Subject: [PATCH 11/44] Fix slider circle masks blocking input for now --- .../Edit/Layers/Selection/Overlays/SliderCircleMask.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 586b516a11..fa9896ff7b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -3,6 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -38,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays Scale = slider.HeadCircle.Scale; AddInternal(new RingPiece()); + + State = Visibility.Visible; } [BackgroundDependencyLoader] @@ -52,5 +56,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays RelativeAnchorPosition = hitObject.RelativeAnchorPosition; } + + // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => false; } } From 6767dd3d4aa51b0947ed54cad45e9fbbaa5cdb26 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:42:42 +0900 Subject: [PATCH 12/44] Fix hitobject masks dying with no recovery --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 8b0d40dadc..c55e34f548 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Edit public readonly DrawableHitObject HitObject; protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; + public override bool RemoveWhenNotAlive => false; public override bool HandleMouseInput => HitObject.IsPresent; public HitObjectMask(DrawableHitObject hitObject) From 6b2ca3665776831e7bbdfac47eb49252090ac014 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Mar 2018 22:52:42 +0900 Subject: [PATCH 13/44] Add license header --- osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index f28f47ba48..0817541a21 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; From 4ad776bfde4c88da610e388c4394a836af444772 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 14:14:04 +0900 Subject: [PATCH 14/44] Make slider circle masks not handle mouse input at all --- .../Edit/Layers/Selection/Overlays/SliderCircleMask.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index fa9896ff7b..4e22b4f693 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -58,6 +57,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays } // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => false; + public override bool HandleMouseInput => false; } } From 082e5e4949f91267389a7a639667fa823db87a11 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:06:52 +0900 Subject: [PATCH 15/44] Reduce iterations of DragBox --- .../Screens/Edit/Screens/Compose/Layers/DragBox.cs | 12 ++++++------ .../Screens/Compose/Layers/HitObjectMaskLayer.cs | 12 +++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index 0817541a21..1de78d19ce 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -24,17 +24,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action DragEnd; - private readonly IEnumerable hitObjectMasks; + private readonly IEnumerable selectableMasks; private Drawable box; /// /// Creates a new . /// - /// The selectable s. - public DragBox(IEnumerable hitObjectMasks) + /// The selectable s. + public DragBox(IEnumerable selectableMasks) { - this.hitObjectMasks = hitObjectMasks; + this.selectableMasks = selectableMasks; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -79,9 +79,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in hitObjectMasks) + foreach (var mask in selectableMasks) { - if (mask.IsAlive && mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) + if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) mask.Select(); else mask.Deselect(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ca161a6f37..2c8a308d5b 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly Playfield playfield; private readonly HitObjectComposer composer; - private Container maskContainer; + private MaskContainer maskContainer; private SelectionBox selectionBox; private readonly HashSet selectedMasks = new HashSet(); @@ -34,10 +34,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers [BackgroundDependencyLoader] private void load() { - maskContainer = new Container(); + maskContainer = new MaskContainer(); + selectionBox = composer.CreateSelectionBox(); - var dragBox = new DragBox(maskContainer); + var dragBox = new DragBox(maskContainer.AliveChildren); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] @@ -104,5 +105,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Deselects all selected s. /// public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + + private class MaskContainer : Container + { + public new IEnumerable AliveChildren => AliveInternalChildren.Cast(); + } } } From 1dca1663c32988040fa47667e8dc52b7f0994d7b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:50:55 +0900 Subject: [PATCH 16/44] Handle all selection events within SelectionBox (incl. single-mask) --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 31 ----- .../Edit/Screens/Compose/Layers/DragBox.cs | 11 +- .../Compose/Layers/HitObjectMaskLayer.cs | 35 +----- .../Screens/Compose/Layers/MaskContainer.cs | 50 ++++++++ .../Screens/Compose/Layers/SelectionBox.cs | 109 ++++++++++-------- 6 files changed, 120 insertions(+), 120 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1a587bf8f5..7d5702ccbf 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -253,8 +253,8 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// - /// The overlays. - public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); + /// The container. + public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(maskContainer); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index c55e34f548..981e109747 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -4,8 +4,6 @@ using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; -using osu.Framework.Input; -using osu.Game.Rulesets.Edit.Types; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; @@ -26,12 +24,6 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; - /// - /// Invoked when this is requesting to be the single selection. - /// This has not been selected at this point, but will be selected immediately afterwards. - /// - public event Action SingleSelectionRequested; - /// /// The which this applies to. /// @@ -77,29 +69,6 @@ namespace osu.Game.Rulesets.Edit return true; } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - SingleSelectionRequested?.Invoke(this); - Select(); - return true; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - // Todo: Various forms of snapping - switch (HitObject.HitObject) - { - case IHasEditablePosition editablePosition: - editablePosition.OffsetPosition(state.Mouse.Delta); - break; - } - return true; - } - - protected override bool OnDragEnd(InputState state) => true; - protected override void PopIn() => Alpha = 1; protected override void PopOut() => Alpha = 0; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs index 1de78d19ce..ea170a0326 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,17 +23,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action DragEnd; - private readonly IEnumerable selectableMasks; + private readonly MaskContainer maskContainer; private Drawable box; /// /// Creates a new . /// - /// The selectable s. - public DragBox(IEnumerable selectableMasks) + /// The selectable s. + public DragBox(MaskContainer maskContainer) { - this.selectableMasks = selectableMasks; + this.maskContainer = maskContainer; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; @@ -79,7 +78,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in selectableMasks) + foreach (var mask in maskContainer.AliveMasks) { if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) mask.Select(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 2c8a308d5b..f972f9ac81 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -21,8 +20,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private MaskContainer maskContainer; private SelectionBox selectionBox; - private readonly HashSet selectedMasks = new HashSet(); - public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { this.playfield = playfield; @@ -36,9 +33,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - selectionBox = composer.CreateSelectionBox(); + selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragBox(maskContainer.AliveChildren); + var dragBox = new DragBox(maskContainer); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] @@ -63,12 +60,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask == null) return; - mask.Selected += onSelected; - mask.Deselected += onDeselected; - mask.SingleSelectionRequested += onSingleSelectionRequested; - maskContainer.Add(mask); - selectionBox.AddMask(mask); } /// @@ -81,34 +73,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask == null) return; - mask.Selected -= onSelected; - mask.Deselected -= onDeselected; - mask.SingleSelectionRequested -= onSingleSelectionRequested; - maskContainer.Remove(mask); - selectionBox.RemoveMask(mask); } - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - private void onDeselected(HitObjectMask mask) => selectedMasks.Remove(mask); - - private void onSingleSelectionRequested(HitObjectMask mask) => DeselectAll(); - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - DeselectAll(); + selectionBox.DeselectAll(); return true; } - - /// - /// Deselects all selected s. - /// - public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); - - private class MaskContainer : Container - { - public new IEnumerable AliveChildren => AliveInternalChildren.Cast(); - } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs new file mode 100644 index 0000000000..4b3ea077bc --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; + +namespace osu.Game.Screens.Edit.Screens.Compose.Layers +{ + public class MaskContainer : Container + { + /// + /// Invoked when any is selected. + /// + public event Action MaskSelected; + + /// + /// Invoked when any is deselected. + /// + public event Action MaskDeselected; + + /// + /// All the s with == true. + /// + public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + + public override void Add(HitObjectMask drawable) + { + base.Add(drawable); + + drawable.Selected += onMaskSelected; + drawable.Deselected += onMaskDeselected; + } + + public override bool Remove(HitObjectMask drawable) + { + var result = base.Remove(drawable); + + if (result) + { + drawable.Selected -= onMaskSelected; + drawable.Deselected += onMaskDeselected; + } + + return result; + } + + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); + private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + } +} diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs index ad8c846bbf..833c94d3f4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -23,15 +22,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly HashSet selectedMasks = new HashSet(); + private readonly MaskContainer maskContainer; + + private readonly List selectedMasks = new List(); + private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable box; - public SelectionBox() + public SelectionBox(MaskContainer maskContainer) { + this.maskContainer = maskContainer; + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; + + maskContainer.MaskSelected += onSelected; + maskContainer.MaskDeselected += onDeselected; } [BackgroundDependencyLoader] @@ -51,58 +58,34 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers }; } - /// - /// Tracks a selectable . - /// - /// The to track. - public void AddMask(HitObjectMask mask) + #region User Input Handling + + // Only handle input on selectable or selected masks + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - mask.Selected += onSelected; - mask.Deselected += onDeselected; - mask.SingleSelectionRequested += onSingleSelectionRequested; - } + if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + return true; - /// - /// Stops tracking a . - /// - /// The to stop tracking. - public void RemoveMask(HitObjectMask mask) - { - mask.Selected -= onSelected; - mask.Deselected -= onDeselected; - mask.SingleSelectionRequested -= onSingleSelectionRequested; - } + DeselectAll(); + selectableMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); - - private void onDeselected(HitObjectMask mask) - { - selectedMasks.Remove(mask); - - // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection - if (selectedMasks.Count == 0) - UpdateVisibility(); - } - - private void onSingleSelectionRequested(HitObjectMask mask) - { - selectedMasks.Add(mask); UpdateVisibility(); + return true; } - // Only handle input on the selected masks - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectedMasks.Any(m => m.ReceiveMouseInputAt(screenSpacePos)); - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - protected override bool OnClick(InputState state) { - if (state.Mouse.NativeState.PositionMouseDown == null) - throw new InvalidOperationException("Click event received without a mouse down position."); + if (selectedMasks.Count == 1) + return true; - // We handled mousedown, but if the mouse has been clicked and not dragged, select the mask which would've handled the mouse down - // A mousedown event is triggered such that a single selection is requested - selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); + var toSelect = selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); + + DeselectAll(); + toSelect.Select(); + + UpdateVisibility(); return true; } @@ -127,10 +110,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnDragEnd(InputState state) => true; + #endregion + + #region Selection Handling + + private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + private void onDeselected(HitObjectMask mask) + { + selectedMasks.Remove(mask); + + // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection + if (selectedMasks.Count == 0) + UpdateVisibility(); + } + + /// + /// Deselects all selected s. + /// + public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + + #endregion + /// /// Updates whether this is visible. /// - public void UpdateVisibility() + internal void UpdateVisibility() { if (selectedMasks.Count > 0) Show(); @@ -145,8 +150,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (selectedMasks.Count == 0) return; - // Todo: We might need to optimise this - // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); @@ -165,5 +168,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Size = bottomRight - topLeft; box.Position = topLeft; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + maskContainer.MaskSelected -= onSelected; + maskContainer.MaskDeselected -= onDeselected; + } } } From 5d0a636cc4fc24ccaa3f868007a97d3334a81d87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:51:38 +0900 Subject: [PATCH 17/44] Rename SelectionBox -> Selection --- .../Compose/Layers/{SelectionBox.cs => Selection.cs} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{SelectionBox.cs => Selection.cs} (93%) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs similarity index 93% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 833c94d3f4..661929c5b4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly List selectedMasks = new List(); private IEnumerable selectableMasks => maskContainer.AliveMasks; - private Drawable box; + private Drawable outline; public SelectionBox(MaskContainer maskContainer) { @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = box = new Container + InternalChild = outline = new Container { Masking = true, BorderThickness = BORDER_RADIUS, @@ -165,8 +165,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers topLeft -= new Vector2(5); bottomRight += new Vector2(5); - box.Size = bottomRight - topLeft; - box.Position = topLeft; + outline.Size = bottomRight - topLeft; + outline.Position = topLeft; } protected override void Dispose(bool isDisposing) From 53541a5c8da1a154c9a0d823704db500f9d59d0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:53:31 +0900 Subject: [PATCH 18/44] Add license header --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 4b3ea077bc..0662070857 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Containers; From 69a7ddbf1e8ff160fcdd3fcc5a43e41e7b87e939 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 16:23:17 +0900 Subject: [PATCH 19/44] Fix ordering of display/input of HitObjectMasks --- .../Edit/Screens/Compose/Layers/MaskContainer.cs | 15 +++++++++++++++ .../Edit/Screens/Compose/Layers/Selection.cs | 11 +++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 0662070857..89bae004b5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; @@ -49,5 +50,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + + protected override int Compare(Drawable x, Drawable y) + { + if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask)) + return base.Compare(x, y); + return Compare(xMask, yMask); + } + + public int Compare(HitObjectMask x, HitObjectMask y) + { + // Put earlier hitobjects towards the end of the list, so they handle input first + int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); + return i == 0 ? CompareReverseChildID(x, y) : i; + } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 661929c5b4..4ff1284f8a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; @@ -24,7 +25,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly MaskContainer maskContainer; - private readonly List selectedMasks = new List(); + private readonly SortedList selectedMasks; private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable outline; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { this.maskContainer = maskContainer; + selectedMasks = new SortedList(maskContainer.Compare); + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; @@ -61,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling // Only handle input on selectable or selected masks - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { @@ -69,7 +72,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return true; DeselectAll(); - selectableMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); + selectableMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); UpdateVisibility(); return true; @@ -80,7 +83,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (selectedMasks.Count == 1) return true; - var toSelect = selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); + var toSelect = selectedMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); DeselectAll(); toSelect.Select(); From f1f7d978ec92be92183755cd9a1fa646e1d3e1b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 16:28:59 +0900 Subject: [PATCH 20/44] Add some comments --- osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 4ff1284f8a..427acbef5a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { + // If masks are overlapping, make sure we don't change the selection if the overlapped portion is pressed if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) return true; @@ -80,6 +81,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers protected override bool OnClick(InputState state) { + // If there's only mask, this isn't going to change anything, so we can save on doing some processing here if (selectedMasks.Count == 1) return true; From 4f19059e55c381c6a7410a8c4bae8eaf5f465dc5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Apr 2018 21:29:49 +0900 Subject: [PATCH 21/44] DragBox -> DragLayer --- .../Screens/Compose/Layers/{DragBox.cs => DragLayer.cs} | 8 ++++---- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{DragBox.cs => DragLayer.cs} (89%) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs similarity index 89% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index ea170a0326..5ad2aeb0e2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragBox.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -14,9 +14,9 @@ using OpenTK.Graphics; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { /// - /// A box that handles and displays drag selection for a collection of s. + /// A layer that handles and displays drag selection for a collection of s. /// - public class DragBox : CompositeDrawable + public class DragLayer : CompositeDrawable { /// /// Invoked when the drag selection has finished. @@ -28,10 +28,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private Drawable box; /// - /// Creates a new . + /// Creates a new . /// /// The selectable s. - public DragBox(MaskContainer maskContainer) + public DragLayer(MaskContainer maskContainer) { this.maskContainer = maskContainer; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index f972f9ac81..259bddce46 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragBox(maskContainer); + var dragBox = new DragLayer(maskContainer); dragBox.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] From d453c2589a33d9c303850a6dcc5a98ee8f5355a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:02:20 +0900 Subject: [PATCH 22/44] Add an explanatory comment for weird override --- osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs index 427acbef5a..f81aa440ac 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs @@ -63,7 +63,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - // Only handle input on selectable or selected masks + /// + /// Handle input on currently selectable or already selected masks. + /// Keep in mind that selectedMasks may contain masks for non-current objects, which we still want to handle input while selected. + /// public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) From 4196bb8c245d4b79d1d7d1ebb3f56b06708618c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:24:13 +0900 Subject: [PATCH 23/44] Move selection logic to MaskContainer --- .../Edit/Screens/Compose/Layers/DragLayer.cs | 9 +-------- .../Edit/Screens/Compose/Layers/MaskContainer.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 5ad2aeb0e2..8ed9cab79d 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -78,14 +78,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - foreach (var mask in maskContainer.AliveMasks) - { - if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) - mask.Select(); - else - mask.Deselect(); - } - + maskContainer.Select(dragRectangle); return true; } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 89bae004b5..9aea17844a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; +using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; namespace osu.Game.Screens.Edit.Screens.Compose.Layers { @@ -48,6 +49,21 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return result; } + /// + /// Select all masks in a given rectangle selection area. + /// + /// The rectangle to perform a selection on in screen-space coordinates. + public void Select(RectangleF rect) + { + foreach (var mask in AliveMasks) + { + if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) + mask.Select(); + else + mask.Deselect(); + } + } + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); From c712b29b5b28f0ac30c624fc34b2e67f991c75d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 16:24:27 +0900 Subject: [PATCH 24/44] Rename dragBox to dragLayer --- .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 259bddce46..ca8525b842 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,15 +35,15 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragBox = new DragLayer(maskContainer); - dragBox.DragEnd += () => selectionBox.UpdateVisibility(); + var dragLayer = new DragLayer(maskContainer); + dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); InternalChildren = new Drawable[] { - dragBox, + dragLayer, maskContainer, selectionBox, - dragBox.CreateProxy() + dragLayer.CreateProxy() }; foreach (var obj in playfield.HitObjects.Objects) From c2d371797ea4f5405ca28992e28ed9f1df500ad0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 17:38:34 +0900 Subject: [PATCH 25/44] Fix unbind failure --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 9aea17844a..9367d9e2dc 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (result) { drawable.Selected -= onMaskSelected; - drawable.Deselected += onMaskDeselected; + drawable.Deselected -= onMaskDeselected; } return result; From b6b8c5165729be6489fe0915eecbcf5d4a508d20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:20:32 +0900 Subject: [PATCH 26/44] Remove DragLayer dependency on MaskContainer --- .../Screens/Edit/Screens/Compose/Layers/DragLayer.cs | 11 +++++------ .../Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 8ed9cab79d..376747ee20 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -18,23 +18,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public class DragLayer : CompositeDrawable { + private readonly Action performSelection; + /// /// Invoked when the drag selection has finished. /// public event Action DragEnd; - private readonly MaskContainer maskContainer; - private Drawable box; /// /// Creates a new . /// /// The selectable s. - public DragLayer(MaskContainer maskContainer) + public DragLayer(Action performSelection) { - this.maskContainer = maskContainer; - + this.performSelection = performSelection; RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; @@ -78,7 +77,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers box.Position = topLeft; box.Size = bottomRight - topLeft; - maskContainer.Select(dragRectangle); + performSelection?.Invoke(dragRectangle); return true; } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ca8525b842..ad8e752d19 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers selectionBox = composer.CreateSelectionBox(maskContainer); - var dragLayer = new DragLayer(maskContainer); dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); + var dragLayer = new DragLayer(maskContainer.Select); InternalChildren = new Drawable[] { From 4d71f2084cdd40d2d5c471e3150e863dfc84b088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:21:27 +0900 Subject: [PATCH 27/44] Move individual mask selection logic out of MaskSelection --- .../Visual/TestCaseEditorSelectionLayer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 17 +++- .../Edit/Screens/Compose/Layers/DragLayer.cs | 2 +- .../Compose/Layers/HitObjectMaskLayer.cs | 20 ++--- .../Screens/Compose/Layers/MaskContainer.cs | 17 +++- .../Layers/{Selection.cs => MaskSelection.cs} | 77 +++++++++---------- 7 files changed, 82 insertions(+), 57 deletions(-) rename osu.Game/Screens/Edit/Screens/Compose/Layers/{Selection.cs => MaskSelection.cs} (69%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 4e39548b5b..1d110477f7 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionBox), + typeof(MaskSelection), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), typeof(HitObjectMaskLayer), diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 4cd5d857c0..d87d00d11f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -248,11 +248,11 @@ namespace osu.Game.Rulesets.Edit public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s + /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// /// The container. - public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(maskContainer); + public virtual MaskSelection CreateSelectionBox(MaskContainer maskContainer) => new MaskSelection(maskContainer); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 981e109747..ed6df54722 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Framework.Input; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; @@ -24,6 +25,13 @@ namespace osu.Game.Rulesets.Edit /// public event Action Deselected; + /// + /// Invoked when this has rqeuested selection. + /// Will fire even if already selected. + /// Does not actually perform selection. + /// + public event Action SelectionRequested; + /// /// The which this applies to. /// @@ -31,7 +39,8 @@ namespace osu.Game.Rulesets.Edit protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; public override bool RemoveWhenNotAlive => false; - public override bool HandleMouseInput => HitObject.IsPresent; + + public override bool HandleMouseInput => ShouldBeAlive; public HitObjectMask(DrawableHitObject hitObject) { @@ -55,6 +64,12 @@ namespace osu.Game.Rulesets.Edit return true; } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + SelectionRequested?.Invoke(this); + return base.OnMouseDown(state, args); + } + /// /// Deselects this , causing it to become invisible. /// diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 376747ee20..67dc45a7b2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionBox.BORDER_RADIUS, + BorderThickness = MaskSelection.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index ad8e752d19..06ae9140bf 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private MaskContainer maskContainer; - private SelectionBox selectionBox; + private MaskSelection maskSelection; public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -33,16 +33,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - selectionBox = composer.CreateSelectionBox(maskContainer); + maskSelection = composer.CreateSelectionBox(maskContainer); - dragLayer.DragEnd += () => selectionBox.UpdateVisibility(); var dragLayer = new DragLayer(maskContainer.Select); + dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); InternalChildren = new Drawable[] { dragLayer, + maskSelection, maskContainer, - selectionBox, dragLayer.CreateProxy() }; @@ -50,6 +50,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers addMask(obj); } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + maskContainer.DeselectAll(); + return true; + } + /// /// Adds a mask for a which adds movement support. /// @@ -75,11 +81,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.Remove(mask); } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - selectionBox.DeselectAll(); - return true; - } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 9367d9e2dc..4cfca2c93a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -23,17 +23,25 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskDeselected; + public event Action MaskSelectionRequested; + /// /// All the s with == true. /// public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + public MaskContainer() + { + RelativeSizeAxes = Axes.Both; + } + public override void Add(HitObjectMask drawable) { base.Add(drawable); drawable.Selected += onMaskSelected; drawable.Deselected += onMaskDeselected; + drawable.SelectionRequested += onSelectionRequested; } public override bool Remove(HitObjectMask drawable) @@ -44,6 +52,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { drawable.Selected -= onMaskSelected; drawable.Deselected -= onMaskDeselected; + drawable.SelectionRequested -= onSelectionRequested; } return result; @@ -59,13 +68,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); - else - mask.Deselect(); } } + /// + /// Deselects all selected s. + /// + public void DeselectAll() => AliveMasks.ToList().ForEach(m => m.Deselect()); + private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + private void onSelectionRequested(HitObjectMask mask) => MaskSelectionRequested?.Invoke(mask); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs similarity index 69% rename from osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index f81aa440ac..666fe16afb 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/Selection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -19,19 +18,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// A box which surrounds s and provides interactive handles, context menus etc. /// - public class SelectionBox : CompositeDrawable + public class MaskSelection : CompositeDrawable { public const float BORDER_RADIUS = 2; private readonly MaskContainer maskContainer; private readonly SortedList selectedMasks; - private IEnumerable selectableMasks => maskContainer.AliveMasks; private Drawable outline; - public SelectionBox(MaskContainer maskContainer) + public MaskSelection(MaskContainer maskContainer) { + // todo: remove this this.maskContainer = maskContainer; selectedMasks = new SortedList(maskContainer.Compare); @@ -42,6 +41,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected += onSelected; maskContainer.MaskDeselected += onDeselected; + maskContainer.MaskSelectionRequested += onSelectionRequested; } [BackgroundDependencyLoader] @@ -63,42 +63,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - /// - /// Handle input on currently selectable or already selected masks. - /// Keep in mind that selectedMasks may contain masks for non-current objects, which we still want to handle input while selected. - /// - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos)); + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); + + protected override bool OnDragStart(InputState state) => handleInput(state); + + protected override bool OnDragEnd(InputState state) => true; + + private bool handleInput(InputState state) { - // If masks are overlapping, make sure we don't change the selection if the overlapped portion is pressed - if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) - return true; - - DeselectAll(); - selectableMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select(); + if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + return false; UpdateVisibility(); return true; } - protected override bool OnClick(InputState state) - { - // If there's only mask, this isn't going to change anything, so we can save on doing some processing here - if (selectedMasks.Count == 1) - return true; - - var toSelect = selectedMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)); - - DeselectAll(); - toSelect.Select(); - - UpdateVisibility(); - return true; - } - - protected override bool OnDragStart(InputState state) => true; - protected override bool OnDrag(InputState state) { // Todo: Various forms of snapping @@ -116,8 +97,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return true; } - protected override bool OnDragEnd(InputState state) => true; - #endregion #region Selection Handling @@ -133,15 +112,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers UpdateVisibility(); } - /// - /// Deselects all selected s. - /// - public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); + private void onSelectionRequested(HitObjectMask mask) + { + if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) + { + if (mask.State == Visibility.Visible) + // we don't want this deselection to affect input for this frame. + Schedule(() => mask.Deselect()); + else + mask.Select(); + } + else + { + if (mask.State == Visibility.Visible) + return; + + maskContainer.DeselectAll(); + mask.Select(); + } + + UpdateVisibility(); + } #endregion /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// internal void UpdateVisibility() { @@ -183,6 +179,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected -= onSelected; maskContainer.MaskDeselected -= onDeselected; + maskContainer.MaskSelectionRequested -= onSelectionRequested; } } } From bce114a37b018d67b3eafd4b17a91758f9bba25a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 18:55:17 +0900 Subject: [PATCH 28/44] Make AliveMasks private --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 4cfca2c93a..cbe064d179 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -25,10 +25,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public event Action MaskSelectionRequested; - /// - /// All the s with == true. - /// - public IEnumerable AliveMasks => AliveInternalChildren.Cast(); + private IEnumerable aliveMasks => AliveInternalChildren.Cast(); public MaskContainer() { @@ -64,7 +61,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// The rectangle to perform a selection on in screen-space coordinates. public void Select(RectangleF rect) { - foreach (var mask in AliveMasks) + foreach (var mask in aliveMasks) { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); @@ -74,7 +71,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Deselects all selected s. /// - public void DeselectAll() => AliveMasks.ToList().ForEach(m => m.Deselect()); + public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); From 31a7db0a35555a34574a7d5239f24a9553caf1c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 19:42:59 +0900 Subject: [PATCH 29/44] Fix drag mishaps --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 666fe16afb..65e85d29bc 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -63,8 +63,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); protected override bool OnDragStart(InputState state) => handleInput(state); @@ -73,7 +71,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private bool handleInput(InputState state) { - if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) + if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown ?? state.Mouse.NativeState.Position))) return false; UpdateVisibility(); From a997ec6139bc46672a49a72a8ca986fab2e3b2a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 19:51:56 +0900 Subject: [PATCH 30/44] Fix ShouldBeAlive state --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index ed6df54722..79c671f335 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -37,10 +37,9 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject HitObject; - protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; - public override bool RemoveWhenNotAlive => false; - + protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == Visibility.Visible; public override bool HandleMouseInput => ShouldBeAlive; + public override bool RemoveWhenNotAlive => false; public HitObjectMask(DrawableHitObject hitObject) { From 5c036b966b30eb1f2619a10f964d57f429fbdb5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:02:38 +0900 Subject: [PATCH 31/44] Formatting fixes --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 5 ++--- osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs | 1 + .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 79c671f335..910da712b4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -26,9 +26,8 @@ namespace osu.Game.Rulesets.Edit public event Action Deselected; /// - /// Invoked when this has rqeuested selection. - /// Will fire even if already selected. - /// Does not actually perform selection. + /// Invoked when this has requested selection. + /// Will fire even if already selected. Does not actually perform selection. /// public event Action SelectionRequested; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs index 67dc45a7b2..51bb61b607 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs @@ -34,6 +34,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public DragLayer(Action performSelection) { this.performSelection = performSelection; + RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index cbe064d179..c29a254cab 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskDeselected; + /// + /// Invoked when any requests selection. + /// public event Action MaskSelectionRequested; private IEnumerable aliveMasks => AliveInternalChildren.Cast(); From 2b15555ede43b3657e75a6e14d669c76569f0b76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:05:01 +0900 Subject: [PATCH 32/44] Remove MaskContainer dependency in MaskSelection --- osu-framework | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +- .../Compose/Layers/HitObjectMaskLayer.cs | 8 ++- .../Screens/Compose/Layers/MaskContainer.cs | 2 +- .../Screens/Compose/Layers/MaskSelection.cs | 49 ++++++++++--------- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/osu-framework b/osu-framework index 6852878ce3..e72c85be22 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 6852878ce30e1bfde301282563d09c7927d9106c +Subproject commit e72c85be22b9d853df075b965cdd433eb9deccf3 diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d87d00d11f..9b33ad2563 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -251,8 +251,7 @@ namespace osu.Game.Rulesets.Edit /// Creates a which outlines s /// and handles hitobject pattern adjustments. /// - /// The container. - public virtual MaskSelection CreateSelectionBox(MaskContainer maskContainer) => new MaskSelection(maskContainer); + public virtual MaskSelection CreateMaskSelection() => new MaskSelection(); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 06ae9140bf..c407f363a5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -18,7 +18,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private readonly HitObjectComposer composer; private MaskContainer maskContainer; - private MaskSelection maskSelection; public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { @@ -33,7 +32,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { maskContainer = new MaskContainer(); - maskSelection = composer.CreateSelectionBox(maskContainer); + var maskSelection = composer.CreateMaskSelection(); + + maskContainer.MaskSelected += maskSelection.HandleSelected; + maskContainer.MaskDeselected += maskSelection.HandleDeselected; + maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; + maskSelection.DeselectAll = maskContainer.DeselectAll; var dragLayer = new DragLayer(maskContainer.Select); dragLayer.DragEnd += () => maskSelection.UpdateVisibility(); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index c29a254cab..401cd16fef 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return Compare(xMask, yMask); } - public int Compare(HitObjectMask x, HitObjectMask y) + public static int Compare(HitObjectMask x, HitObjectMask y) { // Put earlier hitobjects towards the end of the list, so they handle input first int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 65e85d29bc..c874d84997 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,26 +23,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly MaskContainer maskContainer; - private readonly SortedList selectedMasks; private Drawable outline; - public MaskSelection(MaskContainer maskContainer) + public MaskSelection() { - // todo: remove this - this.maskContainer = maskContainer; - - selectedMasks = new SortedList(maskContainer.Compare); + selectedMasks = new SortedList(MaskContainer.Compare); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; Alpha = 0; - - maskContainer.MaskSelected += onSelected; - maskContainer.MaskDeselected += onDeselected; - maskContainer.MaskSelectionRequested += onSelectionRequested; } [BackgroundDependencyLoader] @@ -99,9 +91,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region Selection Handling - private void onSelected(HitObjectMask mask) => selectedMasks.Add(mask); + /// + /// Bind an action to deselect all selected masks. + /// + public Action DeselectAll { private get; set; } - private void onDeselected(HitObjectMask mask) + /// + /// Handle a mask becoming selected. + /// + /// The mask. + public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask); + + /// + /// Handle a mask becoming deselected. + /// + /// The mask. + public void HandleDeselected(HitObjectMask mask) { selectedMasks.Remove(mask); @@ -110,7 +115,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers UpdateVisibility(); } - private void onSelectionRequested(HitObjectMask mask) + /// + /// Handle a mask requesting selection. + /// + /// The mask. + public void HandleSelectionRequested(HitObjectMask mask) { if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) { @@ -125,7 +134,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask.State == Visibility.Visible) return; - maskContainer.DeselectAll(); + + DeselectAll?.Invoke(); mask.Select(); } @@ -170,14 +180,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers outline.Size = bottomRight - topLeft; outline.Position = topLeft; } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - maskContainer.MaskSelected -= onSelected; - maskContainer.MaskDeselected -= onDeselected; - maskContainer.MaskSelectionRequested -= onSelectionRequested; - } } } From 94c3f385418263bc5c99af6b3cb99431c4ccfda4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:06:45 +0900 Subject: [PATCH 33/44] Pass down input state instead of parent lookup --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 4 ++-- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 5 +++-- .../Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 910da712b4..741f600a21 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Edit /// Invoked when this has requested selection. /// Will fire even if already selected. Does not actually perform selection. /// - public event Action SelectionRequested; + public event Action SelectionRequested; /// /// The which this applies to. @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Edit protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - SelectionRequested?.Invoke(this); + SelectionRequested?.Invoke(this, state); return base.OnMouseDown(state, args); } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 401cd16fef..cade7daae2 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Rulesets.Edit; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// /// Invoked when any requests selection. /// - public event Action MaskSelectionRequested; + public event Action MaskSelectionRequested; private IEnumerable aliveMasks => AliveInternalChildren.Cast(); @@ -78,7 +79,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); - private void onSelectionRequested(HitObjectMask mask) => MaskSelectionRequested?.Invoke(mask); + private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index c874d84997..249659c2a4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -119,9 +119,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// Handle a mask requesting selection. /// /// The mask. - public void HandleSelectionRequested(HitObjectMask mask) + public void HandleSelectionRequested(HitObjectMask mask, InputState state) { - if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed) + if (state.Keyboard.ControlPressed) { if (mask.State == Visibility.Visible) // we don't want this deselection to affect input for this frame. From b7325d73e8c1ac260a346404d7b8a5c5175ab9aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:44:07 +0900 Subject: [PATCH 34/44] Don't inherit VisbilityContainer --- .../Selection/Overlays/SliderCircleMask.cs | 3 +- osu.Game/Rulesets/Edit/HitObjectMask.cs | 92 +++++++++++++------ .../Screens/Compose/Layers/MaskSelection.cs | 7 +- 3 files changed, 67 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 4e22b4f693..96ff14205e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -40,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays AddInternal(new RingPiece()); - State = Visibility.Visible; + Select(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 741f600a21..4be79df285 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; @@ -13,7 +14,7 @@ namespace osu.Game.Rulesets.Edit /// /// A mask placed above a adding editing functionality. /// - public class HitObjectMask : VisibilityContainer + public class HitObjectMask : CompositeDrawable, IStateful { /// /// Invoked when this has been selected. @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject HitObject; - protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == Visibility.Visible; + protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected; public override bool HandleMouseInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -45,45 +46,72 @@ namespace osu.Game.Rulesets.Edit HitObject = hitObject; AlwaysPresent = true; - State = Visibility.Hidden; + Alpha = 0; + } + + private SelectionState state; + + public event Action StateChanged; + + public SelectionState State + { + get => state; + set + { + if (state == value) return; + + state = value; + switch (state) + { + case SelectionState.Selected: + Show(); + Selected?.Invoke(this); + break; + case SelectionState.NotSelected: + Hide(); + Deselected?.Invoke(this); + break; + } + } } /// /// Selects this , causing it to become visible. /// - /// True if the was selected. False if the was already selected. - public bool Select() - { - if (State == Visibility.Visible) - return false; - - Show(); - Selected?.Invoke(this); - return true; - } - - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - SelectionRequested?.Invoke(this, state); - return base.OnMouseDown(state, args); - } + public void Select() => State = SelectionState.Selected; /// /// Deselects this , causing it to become invisible. /// - /// True if the was deselected. False if the was already deselected. - public bool Deselect() - { - if (State == Visibility.Hidden) - return false; + public void Deselect() => State = SelectionState.NotSelected; - Hide(); - Deselected?.Invoke(this); - return true; + public bool IsSelected => State == SelectionState.Selected; + + private bool selectionRequested; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + selectionRequested = false; + + if (State == SelectionState.NotSelected && !selectionRequested) + { + SelectionRequested?.Invoke(this, state); + selectionRequested = true; + } + + return base.OnMouseDown(state, args); } - protected override void PopIn() => Alpha = 1; - protected override void PopOut() => Alpha = 0; + protected override bool OnClick(InputState state) + { + if (State == SelectionState.Selected && !selectionRequested) + { + selectionRequested = true; + SelectionRequested?.Invoke(this, state); + } + + return base.OnClick(state); + } /// /// The screen-space point that causes this to be selected. @@ -95,4 +123,10 @@ namespace osu.Game.Rulesets.Edit /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } + + public enum SelectionState + { + NotSelected, + Selected + } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 249659c2a4..54cbd9f1b5 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -123,15 +123,14 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (state.Keyboard.ControlPressed) { - if (mask.State == Visibility.Visible) - // we don't want this deselection to affect input for this frame. - Schedule(() => mask.Deselect()); + if (mask.IsSelected) + mask.Deselect(); else mask.Select(); } else { - if (mask.State == Visibility.Visible) + if (mask.IsSelected) return; From 216c4629e025ea71b317c50bd5d796e50a7ef5da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Apr 2018 20:44:22 +0900 Subject: [PATCH 35/44] Fix dragging backwards not deselecting pending selection --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index cade7daae2..aae4822826 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -69,6 +69,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); + else + mask.Deselect(); } } From 0ad4b8a6f8cb7f00bc34a8336294f4672fe1d6a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 15:55:47 +0900 Subject: [PATCH 36/44] Remove TestTestCase No longer necessary as we have restructured tests considerably. --- osu.Game/Tests/TestTestCase.cs | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 osu.Game/Tests/TestTestCase.cs diff --git a/osu.Game/Tests/TestTestCase.cs b/osu.Game/Tests/TestTestCase.cs deleted file mode 100644 index 4efd57095e..0000000000 --- a/osu.Game/Tests/TestTestCase.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osu.Framework.Testing; - -namespace osu.Game.Tests -{ - [TestFixture] - internal class TestTestCase : TestCase - { - // This TestCase is required for nunit to not throw errors - // See: https://github.com/nunit/nunit/issues/1118 - } -} From 345cfb077d415d43c16dedeb51331c41ba2b177b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 21:03:39 +0900 Subject: [PATCH 37/44] No need to sort list any more --- .../Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- .../Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index aae4822826..12b5971051 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers return Compare(xMask, yMask); } - public static int Compare(HitObjectMask x, HitObjectMask y) + public int Compare(HitObjectMask x, HitObjectMask y) { // Put earlier hitobjects towards the end of the list, so they handle input first int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 54cbd9f1b5..38efb1ae45 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -2,13 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; -using osu.Framework.Lists; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Types; @@ -23,13 +23,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public const float BORDER_RADIUS = 2; - private readonly SortedList selectedMasks; + private readonly List selectedMasks; private Drawable outline; public MaskSelection() { - selectedMasks = new SortedList(MaskContainer.Compare); + selectedMasks = new List(); RelativeSizeAxes = Axes.Both; AlwaysPresent = true; From 5749e7156011259b3197716089986a99795fc6b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Apr 2018 21:06:48 +0900 Subject: [PATCH 38/44] Apply review fixes --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 2 +- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index 4be79df285..d7984cdf0c 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Edit { selectionRequested = false; - if (State == SelectionState.NotSelected && !selectionRequested) + if (State == SelectionState.NotSelected) { SelectionRequested?.Invoke(this, state); selectionRequested = true; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 38efb1ae45..67bc5551da 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -133,7 +133,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers if (mask.IsSelected) return; - DeselectAll?.Invoke(); mask.Select(); } From 24b9a8c9838c1801fe9f2a4997375589bf1eb3b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 10:29:34 +0900 Subject: [PATCH 39/44] Allow HitObjectMasks to handle drag events directly --- osu.Game/Rulesets/Edit/HitObjectMask.cs | 16 +++++++++++++++- .../Compose/Layers/HitObjectMaskLayer.cs | 2 ++ .../Screens/Compose/Layers/MaskContainer.cs | 8 ++++++++ .../Screens/Compose/Layers/MaskSelection.cs | 19 +------------------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs index d7984cdf0c..9f055ffc5d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectMask.cs +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -32,6 +32,11 @@ namespace osu.Game.Rulesets.Edit /// public event Action SelectionRequested; + /// + /// Invoked when this has requested drag. + /// + public event Action DragRequested; + /// /// The which this applies to. /// @@ -99,7 +104,7 @@ namespace osu.Game.Rulesets.Edit selectionRequested = true; } - return base.OnMouseDown(state, args); + return IsSelected; } protected override bool OnClick(InputState state) @@ -108,11 +113,20 @@ namespace osu.Game.Rulesets.Edit { selectionRequested = true; SelectionRequested?.Invoke(this, state); + return true; } return base.OnClick(state); } + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + DragRequested?.Invoke(this, state); + return true; + } + /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index c407f363a5..88f865be20 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -37,6 +37,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers maskContainer.MaskSelected += maskSelection.HandleSelected; maskContainer.MaskDeselected += maskSelection.HandleDeselected; maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested; + maskContainer.MaskDragRequested += maskSelection.HandleDrag; + maskSelection.DeselectAll = maskContainer.DeselectAll; var dragLayer = new DragLayer(maskContainer.Select); diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 12b5971051..10c0b15ff8 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -29,6 +29,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public event Action MaskSelectionRequested; + /// + /// Invoked when any requests drag. + /// + public event Action MaskDragRequested; + private IEnumerable aliveMasks => AliveInternalChildren.Cast(); public MaskContainer() @@ -43,6 +48,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers drawable.Selected += onMaskSelected; drawable.Deselected += onMaskDeselected; drawable.SelectionRequested += onSelectionRequested; + drawable.DragRequested += onDragRequested; } public override bool Remove(HitObjectMask drawable) @@ -54,6 +60,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers drawable.Selected -= onMaskSelected; drawable.Deselected -= onMaskDeselected; drawable.SelectionRequested -= onSelectionRequested; + drawable.DragRequested -= onDragRequested; } return result; @@ -82,6 +89,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); + private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 67bc5551da..64041d8ccb 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -55,22 +55,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers #region User Input Handling - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => handleInput(state); - - protected override bool OnDragStart(InputState state) => handleInput(state); - - protected override bool OnDragEnd(InputState state) => true; - - private bool handleInput(InputState state) - { - if (!selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown ?? state.Mouse.NativeState.Position))) - return false; - - UpdateVisibility(); - return true; - } - - protected override bool OnDrag(InputState state) + public void HandleDrag(HitObjectMask m, InputState state) { // Todo: Various forms of snapping @@ -83,8 +68,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers break; } } - - return true; } #endregion From 32e8d93596d95794b4b0d99e1b1ed810ceea2686 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 11:22:36 +0900 Subject: [PATCH 40/44] Fix selection changing when clicking overlapping hitobjects --- .../Screens/Compose/Layers/MaskContainer.cs | 19 +++++++++++++++++-- .../Screens/Compose/Layers/MaskSelection.cs | 1 - 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 10c0b15ff8..5f9d0bd96a 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -86,8 +86,18 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect()); - private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); - private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.Invoke(mask); + private void onMaskSelected(HitObjectMask mask) + { + MaskSelected?.Invoke(mask); + ChangeChildDepth(mask, 1); + } + + private void onMaskDeselected(HitObjectMask mask) + { + MaskDeselected?.Invoke(mask); + ChangeChildDepth(mask, 0); + } + private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state); private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state); @@ -100,6 +110,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public int Compare(HitObjectMask x, HitObjectMask y) { + // dpeth is used to denote selected status (we always want selected masks to handle input first). + int d = x.Depth.CompareTo(y.Depth); + if (d != 0) + return d; + // Put earlier hitobjects towards the end of the list, so they handle input first int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime); return i == 0 ? CompareReverseChildID(x, y) : i; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs index 64041d8ccb..76b8027b07 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From 5426432e463dcca1bb0dfbce8b095b11ab0fb4f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 11:47:21 +0900 Subject: [PATCH 41/44] Fix drag select crashing --- osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs index 5f9d0bd96a..b631628c9e 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers /// The rectangle to perform a selection on in screen-space coordinates. public void Select(RectangleF rect) { - foreach (var mask in aliveMasks) + foreach (var mask in aliveMasks.ToList()) { if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) mask.Select(); From 714326b6066ae523ae0282992d56e76a61e71274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 13:16:27 +0900 Subject: [PATCH 42/44] Fix TestCase not working with dynamic compilation --- osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 1d110477f7..9b50645b0d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Screens.Compose.Layers; using osu.Game.Tests.Beatmaps; @@ -26,13 +25,10 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(MaskSelection), + typeof(DragLayer), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), typeof(HitObjectMaskLayer), - typeof(HitObjectMask), - typeof(HitCircleMask), - typeof(SliderMask), - typeof(SliderCircleMask), typeof(NotNullAttribute) }; From cd48cb18874f328e0df98b21a8d63defe71ddbfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 15:17:39 +0900 Subject: [PATCH 43/44] Add comment --- .../Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 88f865be20..423cf0ed29 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -21,7 +21,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) { + // we need the playfield as HitObjects may not be initialised until its BDL. this.playfield = playfield; + this.composer = composer; RelativeSizeAxes = Axes.Both; From ae2dce254aceb1439cd8dce7cc6efb37de652570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Apr 2018 15:18:01 +0900 Subject: [PATCH 44/44] Rename TestCase --- ...CaseEditorSelectionLayer.cs => TestCaseHitObjectComposer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/{TestCaseEditorSelectionLayer.cs => TestCaseHitObjectComposer.cs} (94%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs rename to osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 9b50645b0d..72d60d8e01 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -20,7 +20,7 @@ using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { [TestFixture] - public class TestCaseEditorSelectionLayer : OsuTestCase + public class TestCaseHitObjectComposer : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] {