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 OpenTK;
|
2018-10-18 15:36:06 +08:00
|
|
|
|
using OpenTK.Input;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-11-06 17:28:22 +08:00
|
|
|
|
namespace osu.Game.Screens.Edit.Compose.Components
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
2018-11-06 16:56:04 +08:00
|
|
|
|
/// A box which surrounds <see cref="SelectionBlueprint"/>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-11-06 17:06:34 +08:00
|
|
|
|
private readonly List<SelectionBlueprint> selectedBlueprints;
|
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-11-06 17:06:34 +08:00
|
|
|
|
selectedBlueprints = new List<SelectionBlueprint>();
|
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-11-07 15:21:32 +08:00
|
|
|
|
public void HandleDrag(DragEvent dragEvent)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
// Todo: Various forms of snapping
|
|
|
|
|
|
2018-11-06 17:06:34 +08:00
|
|
|
|
foreach (var blueprint in selectedBlueprints)
|
2018-11-07 15:21:32 +08:00
|
|
|
|
blueprint.AdjustPosition(dragEvent);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
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:
|
2018-11-06 17:06:34 +08:00
|
|
|
|
foreach (var h in selectedBlueprints.ToList())
|
2018-10-18 15:36:06 +08:00
|
|
|
|
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>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// Bind an action to deselect all selected blueprints.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
public Action DeselectAll { private get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// Handle a blueprint becoming selected.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// <param name="blueprint">The blueprint.</param>
|
|
|
|
|
public void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// Handle a blueprint becoming deselected.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// <param name="blueprint">The blueprint.</param>
|
2018-11-06 16:56:04 +08:00
|
|
|
|
public void HandleDeselected(SelectionBlueprint blueprint)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2018-11-06 17:06:34 +08:00
|
|
|
|
selectedBlueprints.Remove(blueprint);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-11-06 17:06:34 +08:00
|
|
|
|
// We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection
|
|
|
|
|
if (selectedBlueprints.Count == 0)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
UpdateVisibility();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// Handle a blueprint requesting selection.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
/// </summary>
|
2018-11-06 17:06:34 +08:00
|
|
|
|
/// <param name="blueprint">The blueprint.</param>
|
2018-11-06 16:56:04 +08:00
|
|
|
|
public void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
if (state.Keyboard.ControlPressed)
|
|
|
|
|
{
|
2018-11-06 16:56:04 +08:00
|
|
|
|
if (blueprint.IsSelected)
|
|
|
|
|
blueprint.Deselect();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
else
|
2018-11-06 16:56:04 +08:00
|
|
|
|
blueprint.Select();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-11-06 16:56:04 +08:00
|
|
|
|
if (blueprint.IsSelected)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
DeselectAll?.Invoke();
|
2018-11-06 16:56:04 +08:00
|
|
|
|
blueprint.Select();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
2018-11-06 17:06:34 +08:00
|
|
|
|
if (selectedBlueprints.Count > 0)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
Show();
|
|
|
|
|
else
|
|
|
|
|
Hide();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void Update()
|
|
|
|
|
{
|
|
|
|
|
base.Update();
|
|
|
|
|
|
2018-11-06 17:06:34 +08:00
|
|
|
|
if (selectedBlueprints.Count == 0)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
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;
|
|
|
|
|
|
2018-11-06 17:06:34 +08:00
|
|
|
|
foreach (var blueprint in selectedBlueprints)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2018-11-06 17:06:34 +08:00
|
|
|
|
topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft));
|
|
|
|
|
bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight));
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
topLeft -= new Vector2(5);
|
|
|
|
|
bottomRight += new Vector2(5);
|
|
|
|
|
|
|
|
|
|
outline.Size = bottomRight - topLeft;
|
|
|
|
|
outline.Position = topLeft;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|