2018-04-13 17:19:50 +08:00
|
|
|
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
|
|
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2018-10-18 15:36:06 +08:00
|
|
|
|
using System.Linq;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
|
using osu.Framework.Graphics.Containers;
|
|
|
|
|
using osu.Framework.Graphics.Shapes;
|
2018-10-18 15:36:06 +08:00
|
|
|
|
using osu.Framework.Input.Events;
|
2018-07-21 10:38:28 +08:00
|
|
|
|
using osu.Framework.Input.States;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Game.Graphics;
|
|
|
|
|
using osu.Game.Rulesets.Edit;
|
|
|
|
|
using osu.Game.Rulesets.Edit.Types;
|
|
|
|
|
using OpenTK;
|
2018-10-18 15:36:06 +08:00
|
|
|
|
using OpenTK.Input;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Screens.Edit.Screens.Compose.Layers
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
2018-10-03 12:28:00 +08:00
|
|
|
|
/// A box which surrounds <see cref="SelectionMask"/>s and provides interactive handles, context menus etc.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
2018-11-06 16:52:47 +08:00
|
|
|
|
public class SelectionBox : CompositeDrawable
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
public const float BORDER_RADIUS = 2;
|
|
|
|
|
|
2018-10-03 12:28:00 +08:00
|
|
|
|
private readonly List<SelectionMask> selectedMasks;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
private Drawable outline;
|
|
|
|
|
|
2018-10-18 15:36:06 +08:00
|
|
|
|
[Resolved]
|
|
|
|
|
private IPlacementHandler placementHandler { get; set; }
|
|
|
|
|
|
2018-11-06 16:52:47 +08:00
|
|
|
|
public SelectionBox()
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2018-10-03 12:28:00 +08:00
|
|
|
|
selectedMasks = new List<SelectionMask>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
|
|
|
AlwaysPresent = true;
|
|
|
|
|
Alpha = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
|
private void load(OsuColour colours)
|
|
|
|
|
{
|
|
|
|
|
InternalChild = outline = new Container
|
|
|
|
|
{
|
|
|
|
|
Masking = true,
|
|
|
|
|
BorderThickness = BORDER_RADIUS,
|
|
|
|
|
BorderColour = colours.Yellow,
|
|
|
|
|
Child = new Box
|
|
|
|
|
{
|
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
|
AlwaysPresent = true,
|
|
|
|
|
Alpha = 0
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region User Input Handling
|
|
|
|
|
|
2018-10-03 12:28:00 +08:00
|
|
|
|
public void HandleDrag(SelectionMask m, Vector2 delta, InputState state)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
// Todo: Various forms of snapping
|
|
|
|
|
|
|
|
|
|
foreach (var mask in selectedMasks)
|
|
|
|
|
{
|
|
|
|
|
switch (mask.HitObject.HitObject)
|
|
|
|
|
{
|
|
|
|
|
case IHasEditablePosition editablePosition:
|
2018-09-19 19:52:57 +08:00
|
|
|
|
editablePosition.OffsetPosition(delta);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-18 15:36:06 +08:00
|
|
|
|
protected override bool OnKeyDown(KeyDownEvent e)
|
|
|
|
|
{
|
|
|
|
|
if (e.Repeat)
|
|
|
|
|
return base.OnKeyDown(e);
|
|
|
|
|
|
|
|
|
|
switch (e.Key)
|
|
|
|
|
{
|
|
|
|
|
case Key.Delete:
|
|
|
|
|
foreach (var h in selectedMasks.ToList())
|
|
|
|
|
placementHandler.Delete(h.HitObject.HitObject);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-10-31 11:07:06 +08:00
|
|
|
|
|
2018-10-18 15:36:06 +08:00
|
|
|
|
return base.OnKeyDown(e);
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-13 17:19:50 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Selection Handling
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Bind an action to deselect all selected masks.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Action DeselectAll { private get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle a mask becoming selected.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="mask">The mask.</param>
|
2018-10-03 12:28:00 +08:00
|
|
|
|
public void HandleSelected(SelectionMask mask) => selectedMasks.Add(mask);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle a mask becoming deselected.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="mask">The mask.</param>
|
2018-10-03 12:28:00 +08:00
|
|
|
|
public void HandleDeselected(SelectionMask mask)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
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>
|
|
|
|
|
/// Handle a mask requesting selection.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="mask">The mask.</param>
|
2018-10-03 12:28:00 +08:00
|
|
|
|
public void HandleSelectionRequested(SelectionMask mask, InputState state)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
if (state.Keyboard.ControlPressed)
|
|
|
|
|
{
|
|
|
|
|
if (mask.IsSelected)
|
|
|
|
|
mask.Deselect();
|
|
|
|
|
else
|
|
|
|
|
mask.Select();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (mask.IsSelected)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
DeselectAll?.Invoke();
|
|
|
|
|
mask.Select();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdateVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-11-06 16:52:47 +08:00
|
|
|
|
/// Updates whether this <see cref="SelectionBox"/> is visible.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
internal void UpdateVisibility()
|
|
|
|
|
{
|
|
|
|
|
if (selectedMasks.Count > 0)
|
|
|
|
|
Show();
|
|
|
|
|
else
|
|
|
|
|
Hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
{
|
|
|
|
|
base.Update();
|
|
|
|
|
|
|
|
|
|
if (selectedMasks.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Move the rectangle to cover the hitobjects
|
|
|
|
|
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
|
|
|
|
|
var bottomRight = new Vector2(float.MinValue, float.MinValue);
|
|
|
|
|
|
|
|
|
|
bool hasSelection = false;
|
|
|
|
|
|
|
|
|
|
foreach (var mask in selectedMasks)
|
|
|
|
|
{
|
|
|
|
|
topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft));
|
|
|
|
|
bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
topLeft -= new Vector2(5);
|
|
|
|
|
bottomRight += new Vector2(5);
|
|
|
|
|
|
|
|
|
|
outline.Size = bottomRight - topLeft;
|
|
|
|
|
outline.Position = topLeft;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|