Move individual mask selection logic out of MaskSelection

This commit is contained in:
Dean Herbert 2018-04-04 18:21:27 +09:00
parent b6b8c51657
commit 4d71f2084c
7 changed files with 82 additions and 57 deletions

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(SelectionBox), typeof(MaskSelection),
typeof(HitObjectComposer), typeof(HitObjectComposer),
typeof(OsuHitObjectComposer), typeof(OsuHitObjectComposer),
typeof(HitObjectMaskLayer), typeof(HitObjectMaskLayer),

View File

@ -248,11 +248,11 @@ namespace osu.Game.Rulesets.Edit
public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null;
/// <summary> /// <summary>
/// Creates a <see cref="SelectionBox"/> which outlines <see cref="DrawableHitObject"/>s /// Creates a <see cref="MaskSelection"/> which outlines <see cref="DrawableHitObject"/>s
/// and handles hitobject pattern adjustments. /// and handles hitobject pattern adjustments.
/// </summary> /// </summary>
/// <param name="maskContainer">The <see cref="HitObjectMask"/> container.</param> /// <param name="maskContainer">The <see cref="HitObjectMask"/> container.</param>
public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(maskContainer); public virtual MaskSelection CreateSelectionBox(MaskContainer maskContainer) => new MaskSelection(maskContainer);
/// <summary> /// <summary>
/// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>. /// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>.

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
@ -24,6 +25,13 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public event Action<HitObjectMask> Deselected; public event Action<HitObjectMask> Deselected;
/// <summary>
/// Invoked when this <see cref="HitObjectMask"/> has rqeuested selection.
/// Will fire even if already selected.
/// Does not actually perform selection.
/// </summary>
public event Action<HitObjectMask> SelectionRequested;
/// <summary> /// <summary>
/// The <see cref="DrawableHitObject"/> which this <see cref="HitObjectMask"/> applies to. /// The <see cref="DrawableHitObject"/> which this <see cref="HitObjectMask"/> applies to.
/// </summary> /// </summary>
@ -31,7 +39,8 @@ namespace osu.Game.Rulesets.Edit
protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible; protected override bool ShouldBeAlive => HitObject.IsAlive || State == Visibility.Visible;
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
public override bool HandleMouseInput => HitObject.IsPresent;
public override bool HandleMouseInput => ShouldBeAlive;
public HitObjectMask(DrawableHitObject hitObject) public HitObjectMask(DrawableHitObject hitObject)
{ {
@ -55,6 +64,12 @@ namespace osu.Game.Rulesets.Edit
return true; return true;
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
SelectionRequested?.Invoke(this);
return base.OnMouseDown(state, args);
}
/// <summary> /// <summary>
/// Deselects this <see cref="HitObjectMask"/>, causing it to become invisible. /// Deselects this <see cref="HitObjectMask"/>, causing it to become invisible.
/// </summary> /// </summary>

View File

@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = SelectionBox.BORDER_RADIUS, BorderThickness = MaskSelection.BORDER_RADIUS,
Child = new Box Child = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
private readonly HitObjectComposer composer; private readonly HitObjectComposer composer;
private MaskContainer maskContainer; private MaskContainer maskContainer;
private SelectionBox selectionBox; private MaskSelection maskSelection;
public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer)
{ {
@ -33,16 +33,16 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
maskContainer = new MaskContainer(); maskContainer = new MaskContainer();
selectionBox = composer.CreateSelectionBox(maskContainer); maskSelection = composer.CreateSelectionBox(maskContainer);
dragLayer.DragEnd += () => selectionBox.UpdateVisibility();
var dragLayer = new DragLayer(maskContainer.Select); var dragLayer = new DragLayer(maskContainer.Select);
dragLayer.DragEnd += () => maskSelection.UpdateVisibility();
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
dragLayer, dragLayer,
maskSelection,
maskContainer, maskContainer,
selectionBox,
dragLayer.CreateProxy() dragLayer.CreateProxy()
}; };
@ -50,6 +50,12 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
addMask(obj); addMask(obj);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
maskContainer.DeselectAll();
return true;
}
/// <summary> /// <summary>
/// Adds a mask for a <see cref="DrawableHitObject"/> which adds movement support. /// Adds a mask for a <see cref="DrawableHitObject"/> which adds movement support.
/// </summary> /// </summary>
@ -75,11 +81,5 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
maskContainer.Remove(mask); maskContainer.Remove(mask);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
selectionBox.DeselectAll();
return true;
}
} }
} }

View File

@ -23,17 +23,25 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
/// </summary> /// </summary>
public event Action<HitObjectMask> MaskDeselected; public event Action<HitObjectMask> MaskDeselected;
public event Action<HitObjectMask> MaskSelectionRequested;
/// <summary> /// <summary>
/// All the <see cref="HitObjectMask"/>s with <see cref="IsAlive"/> == true. /// All the <see cref="HitObjectMask"/>s with <see cref="IsAlive"/> == true.
/// </summary> /// </summary>
public IEnumerable<HitObjectMask> AliveMasks => AliveInternalChildren.Cast<HitObjectMask>(); public IEnumerable<HitObjectMask> AliveMasks => AliveInternalChildren.Cast<HitObjectMask>();
public MaskContainer()
{
RelativeSizeAxes = Axes.Both;
}
public override void Add(HitObjectMask drawable) public override void Add(HitObjectMask drawable)
{ {
base.Add(drawable); base.Add(drawable);
drawable.Selected += onMaskSelected; drawable.Selected += onMaskSelected;
drawable.Deselected += onMaskDeselected; drawable.Deselected += onMaskDeselected;
drawable.SelectionRequested += onSelectionRequested;
} }
public override bool Remove(HitObjectMask drawable) public override bool Remove(HitObjectMask drawable)
@ -44,6 +52,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
drawable.Selected -= onMaskSelected; drawable.Selected -= onMaskSelected;
drawable.Deselected -= onMaskDeselected; drawable.Deselected -= onMaskDeselected;
drawable.SelectionRequested -= onSelectionRequested;
} }
return result; return result;
@ -59,13 +68,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
if (mask.IsPresent && rect.Contains(mask.SelectionPoint)) if (mask.IsPresent && rect.Contains(mask.SelectionPoint))
mask.Select(); mask.Select();
else
mask.Deselect();
} }
} }
/// <summary>
/// Deselects all selected <see cref="HitObjectMask"/>s.
/// </summary>
public void DeselectAll() => AliveMasks.ToList().ForEach(m => m.Deselect());
private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask); private void onMaskSelected(HitObjectMask mask) => MaskSelected?.Invoke(mask);
private void onMaskDeselected(HitObjectMask mask) => MaskDeselected?.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) protected override int Compare(Drawable x, Drawable y)
{ {

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -19,19 +18,19 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
/// <summary> /// <summary>
/// A box which surrounds <see cref="HitObjectMask"/>s and provides interactive handles, context menus etc. /// A box which surrounds <see cref="HitObjectMask"/>s and provides interactive handles, context menus etc.
/// </summary> /// </summary>
public class SelectionBox : CompositeDrawable public class MaskSelection : CompositeDrawable
{ {
public const float BORDER_RADIUS = 2; public const float BORDER_RADIUS = 2;
private readonly MaskContainer maskContainer; private readonly MaskContainer maskContainer;
private readonly SortedList<HitObjectMask> selectedMasks; private readonly SortedList<HitObjectMask> selectedMasks;
private IEnumerable<HitObjectMask> selectableMasks => maskContainer.AliveMasks;
private Drawable outline; private Drawable outline;
public SelectionBox(MaskContainer maskContainer) public MaskSelection(MaskContainer maskContainer)
{ {
// todo: remove this
this.maskContainer = maskContainer; this.maskContainer = maskContainer;
selectedMasks = new SortedList<HitObjectMask>(maskContainer.Compare); selectedMasks = new SortedList<HitObjectMask>(maskContainer.Compare);
@ -42,6 +41,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
maskContainer.MaskSelected += onSelected; maskContainer.MaskSelected += onSelected;
maskContainer.MaskDeselected += onDeselected; maskContainer.MaskDeselected += onDeselected;
maskContainer.MaskSelectionRequested += onSelectionRequested;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -63,42 +63,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
#region User Input Handling #region User Input Handling
/// <summary> public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
/// 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.
/// </summary>
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Reverse().Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos));
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)))
if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position))) return false;
return true;
DeselectAll();
selectableMasks.Reverse().First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select();
UpdateVisibility(); UpdateVisibility();
return true; 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) protected override bool OnDrag(InputState state)
{ {
// Todo: Various forms of snapping // Todo: Various forms of snapping
@ -116,8 +97,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
return true; return true;
} }
protected override bool OnDragEnd(InputState state) => true;
#endregion #endregion
#region Selection Handling #region Selection Handling
@ -133,15 +112,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
UpdateVisibility(); UpdateVisibility();
} }
/// <summary> private void onSelectionRequested(HitObjectMask mask)
/// Deselects all selected <see cref="HitObjectMask"/>s. {
/// </summary> if (GetContainingInputManager().CurrentState.Keyboard.ControlPressed)
public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect()); {
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 #endregion
/// <summary> /// <summary>
/// Updates whether this <see cref="SelectionBox"/> is visible. /// Updates whether this <see cref="MaskSelection"/> is visible.
/// </summary> /// </summary>
internal void UpdateVisibility() internal void UpdateVisibility()
{ {
@ -183,6 +179,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
maskContainer.MaskSelected -= onSelected; maskContainer.MaskSelected -= onSelected;
maskContainer.MaskDeselected -= onDeselected; maskContainer.MaskDeselected -= onDeselected;
maskContainer.MaskSelectionRequested -= onSelectionRequested;
} }
} }
} }