// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { /// /// A component which outlines s and handles movement of selections. /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler { public const float BORDER_RADIUS = 2; public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; public IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.DrawableObject.HitObject); private Drawable outline; [Resolved] private IPlacementHandler placementHandler { get; set; } public SelectionHandler() { selectedBlueprints = new List(); 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 /// /// Handles the selected s being moved. /// /// The move event. /// Whether any s were moved. public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) { case PlatformActionMethod.Delete: foreach (var h in selectedBlueprints.ToList()) placementHandler.Delete(h.DrawableObject.HitObject); return true; } return false; } public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; #endregion #region Selection Handling /// /// Bind an action to deselect all selected blueprints. /// internal Action DeselectAll { private get; set; } /// /// Handle a blueprint becoming selected. /// /// The blueprint. internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); /// /// Handle a blueprint becoming deselected. /// /// The blueprint. internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection if (selectedBlueprints.Count == 0) UpdateVisibility(); } /// /// Handle a blueprint requesting selection. /// /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { if (blueprint.IsSelected) blueprint.Deselect(); else blueprint.Select(); } else { if (blueprint.IsSelected) return; DeselectAll?.Invoke(); blueprint.Select(); } UpdateVisibility(); } #endregion /// /// Updates whether this is visible. /// internal void UpdateVisibility() { if (selectedBlueprints.Count > 0) Show(); else Hide(); } protected override void Update() { base.Update(); if (selectedBlueprints.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); foreach (var blueprint in selectedBlueprints) { topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(blueprint.SelectionQuad.BottomRight)); } topLeft -= new Vector2(5); bottomRight += new Vector2(5); outline.Size = bottomRight - topLeft; outline.Position = topLeft; } } }