From 1dca1663c32988040fa47667e8dc52b7f0994d7b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Mar 2018 15:50:55 +0900 Subject: [PATCH] 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; + } } }