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; } } }