1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 20:07:29 +08:00

Handle all selection events within SelectionBox (incl. single-mask)

This commit is contained in:
smoogipoo 2018-03-30 15:50:55 +09:00
parent 082e5e4949
commit 1dca1663c3
6 changed files with 120 additions and 120 deletions

View File

@ -253,8 +253,8 @@ namespace osu.Game.Rulesets.Edit
/// Creates a <see cref="SelectionBox"/> which outlines <see cref="DrawableHitObject"/>s /// Creates a <see cref="SelectionBox"/> which outlines <see cref="DrawableHitObject"/>s
/// and handles hitobject pattern adjustments. /// and handles hitobject pattern adjustments.
/// </summary> /// </summary>
/// <param name="overlays">The <see cref="DrawableHitObject"/> overlays.</param> /// <param name="maskContainer">The <see cref="HitObjectMask"/> container.</param>
public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); public virtual SelectionBox CreateSelectionBox(MaskContainer maskContainer) => new SelectionBox(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,8 +4,6 @@
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.Edit.Types;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
@ -26,12 +24,6 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public event Action<HitObjectMask> Deselected; public event Action<HitObjectMask> Deselected;
/// <summary>
/// Invoked when this <see cref="HitObjectMask"/> is requesting to be the single selection.
/// This <see cref="HitObjectMask"/> has not been selected at this point, but will be selected immediately afterwards.
/// </summary>
public event Action<HitObjectMask> SingleSelectionRequested;
/// <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>
@ -77,29 +69,6 @@ namespace osu.Game.Rulesets.Edit
return true; 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 PopIn() => Alpha = 1;
protected override void PopOut() => Alpha = 0; protected override void PopOut() => Alpha = 0;

View File

@ -2,7 +2,6 @@
// 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; using System;
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -24,17 +23,17 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
/// </summary> /// </summary>
public event Action DragEnd; public event Action DragEnd;
private readonly IEnumerable<HitObjectMask> selectableMasks; private readonly MaskContainer maskContainer;
private Drawable box; private Drawable box;
/// <summary> /// <summary>
/// Creates a new <see cref="DragBox"/>. /// Creates a new <see cref="DragBox"/>.
/// </summary> /// </summary>
/// <param name="selectableMasks">The selectable <see cref="HitObjectMask"/>s.</param> /// <param name="maskContainer">The selectable <see cref="HitObjectMask"/>s.</param>
public DragBox(IEnumerable<HitObjectMask> selectableMasks) public DragBox(MaskContainer maskContainer)
{ {
this.selectableMasks = selectableMasks; this.maskContainer = maskContainer;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
AlwaysPresent = true; AlwaysPresent = true;
@ -79,7 +78,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
box.Position = topLeft; box.Position = topLeft;
box.Size = bottomRight - topLeft; box.Size = bottomRight - topLeft;
foreach (var mask in selectableMasks) foreach (var mask in maskContainer.AliveMasks)
{ {
if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint)) if (mask.IsPresent && dragRectangle.Contains(mask.SelectionPoint))
mask.Select(); mask.Select();

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;
@ -21,8 +20,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
private MaskContainer maskContainer; private MaskContainer maskContainer;
private SelectionBox selectionBox; private SelectionBox selectionBox;
private readonly HashSet<HitObjectMask> selectedMasks = new HashSet<HitObjectMask>();
public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer) public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer)
{ {
this.playfield = playfield; this.playfield = playfield;
@ -36,9 +33,9 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
maskContainer = new MaskContainer(); 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(); dragBox.DragEnd += () => selectionBox.UpdateVisibility();
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -63,12 +60,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
if (mask == null) if (mask == null)
return; return;
mask.Selected += onSelected;
mask.Deselected += onDeselected;
mask.SingleSelectionRequested += onSingleSelectionRequested;
maskContainer.Add(mask); maskContainer.Add(mask);
selectionBox.AddMask(mask);
} }
/// <summary> /// <summary>
@ -81,34 +73,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
if (mask == null) if (mask == null)
return; return;
mask.Selected -= onSelected;
mask.Deselected -= onDeselected;
mask.SingleSelectionRequested -= onSingleSelectionRequested;
maskContainer.Remove(mask); 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) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
DeselectAll(); selectionBox.DeselectAll();
return true; return true;
} }
/// <summary>
/// Deselects all selected <see cref="DrawableHitObject"/>s.
/// </summary>
public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect());
private class MaskContainer : Container<HitObjectMask>
{
public new IEnumerable<HitObjectMask> AliveChildren => AliveInternalChildren.Cast<HitObjectMask>();
}
} }
} }

View File

@ -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<HitObjectMask>
{
/// <summary>
/// Invoked when any <see cref="HitObjectMask"/> is selected.
/// </summary>
public event Action<HitObjectMask> MaskSelected;
/// <summary>
/// Invoked when any <see cref="HitObjectMask"/> is deselected.
/// </summary>
public event Action<HitObjectMask> MaskDeselected;
/// <summary>
/// All the <see cref="HitObjectMask"/>s with <see cref="IsAlive"/> == true.
/// </summary>
public IEnumerable<HitObjectMask> AliveMasks => AliveInternalChildren.Cast<HitObjectMask>();
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);
}
}

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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -23,15 +22,23 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
public const float BORDER_RADIUS = 2; public const float BORDER_RADIUS = 2;
private readonly HashSet<HitObjectMask> selectedMasks = new HashSet<HitObjectMask>(); private readonly MaskContainer maskContainer;
private readonly List<HitObjectMask> selectedMasks = new List<HitObjectMask>();
private IEnumerable<HitObjectMask> selectableMasks => maskContainer.AliveMasks;
private Drawable box; private Drawable box;
public SelectionBox() public SelectionBox(MaskContainer maskContainer)
{ {
this.maskContainer = maskContainer;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
AlwaysPresent = true; AlwaysPresent = true;
Alpha = 0; Alpha = 0;
maskContainer.MaskSelected += onSelected;
maskContainer.MaskDeselected += onDeselected;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -51,58 +58,34 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
}; };
} }
/// <summary> #region User Input Handling
/// Tracks a selectable <see cref="HitObjectMask"/>.
/// </summary> // Only handle input on selectable or selected masks
/// <param name="mask">The <see cref="HitObjectMask"/> to track.</param> public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => selectableMasks.Concat(selectedMasks).Any(m => m.ReceiveMouseInputAt(screenSpacePos));
public void AddMask(HitObjectMask mask)
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
mask.Selected += onSelected; if (selectedMasks.Any(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)))
mask.Deselected += onDeselected; return true;
mask.SingleSelectionRequested += onSingleSelectionRequested;
}
/// <summary> DeselectAll();
/// Stops tracking a <see cref="HitObjectMask"/>. selectableMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position)).Select();
/// </summary>
/// <param name="mask">The <see cref="HitObjectMask"/> to stop tracking.</param>
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);
// 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(); 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) protected override bool OnClick(InputState state)
{ {
if (state.Mouse.NativeState.PositionMouseDown == null) if (selectedMasks.Count == 1)
throw new InvalidOperationException("Click event received without a mouse down position."); 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 var toSelect = selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.Position));
// A mousedown event is triggered such that a single selection is requested
selectedMasks.First(m => m.ReceiveMouseInputAt(state.Mouse.NativeState.PositionMouseDown.Value)).TriggerOnMouseDown(state); DeselectAll();
toSelect.Select();
UpdateVisibility();
return true; return true;
} }
@ -127,10 +110,32 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
protected override bool OnDragEnd(InputState state) => true; 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();
}
/// <summary>
/// Deselects all selected <see cref="HitObjectMask"/>s.
/// </summary>
public void DeselectAll() => selectedMasks.ToList().ForEach(m => m.Deselect());
#endregion
/// <summary> /// <summary>
/// Updates whether this <see cref="SelectionBox"/> is visible. /// Updates whether this <see cref="SelectionBox"/> is visible.
/// </summary> /// </summary>
public void UpdateVisibility() internal void UpdateVisibility()
{ {
if (selectedMasks.Count > 0) if (selectedMasks.Count > 0)
Show(); Show();
@ -145,8 +150,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
if (selectedMasks.Count == 0) if (selectedMasks.Count == 0)
return; return;
// Todo: We might need to optimise this
// Move the rectangle to cover the hitobjects // Move the rectangle to cover the hitobjects
var topLeft = new Vector2(float.MaxValue, float.MaxValue); var topLeft = new Vector2(float.MaxValue, float.MaxValue);
var bottomRight = new Vector2(float.MinValue, float.MinValue); 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.Size = bottomRight - topLeft;
box.Position = topLeft; box.Position = topLeft;
} }
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
maskContainer.MaskSelected -= onSelected;
maskContainer.MaskDeselected -= onDeselected;
}
} }
} }