diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5795bb8405..41df7ae4a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -157,6 +157,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => HeadCircle.ApproachCircle; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); + public override Vector2 SelectionPoint => ToScreenSpace(Body.Position); public override Quad SelectionQuad => Body.PathDrawQuad; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 901d1c568d..89af67ba2a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -78,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces container.Attach(RenderbufferInternalFormat.DepthComponent16); } + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos); + public void SetRange(double p0, double p1) { if (p0 > p1) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 755800c4e1..ae5296b70e 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -19,7 +19,12 @@ namespace osu.Game.Tests.Visual { public class TestCaseEditorSelectionLayer : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HitObjectCapturer), + typeof(HitObjectSelectionBox), + typeof(SelectionLayer) + }; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs new file mode 100644 index 0000000000..e141f06816 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class HitObjectCapturer + { + public event Action HitObjectCaptured; + + private readonly IEnumerable capturableHitObjects; + + public HitObjectCapturer(IEnumerable capturableHitObjects) + { + this.capturableHitObjects = capturableHitObjects; + } + + /// + /// Captures all hitobjects that are present within the area of a . + /// + /// The capture . + /// If any s were captured. + public bool CaptureQuad(Quad screenSpaceQuad) + { + bool anyCaptured = false; + foreach (var obj in capturableHitObjects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) + { + HitObjectCaptured?.Invoke(obj); + anyCaptured = true; + } + + return anyCaptured; + } + + /// + /// Captures the top-most hitobject that is present under a specific point. + /// + /// The to capture at. + /// Whether a was captured. + public bool CapturePoint(Vector2 screenSpacePoint) + { + var captured = capturableHitObjects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); + if (captured == null) + return false; + + HitObjectCaptured?.Invoke(captured); + return true; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index fcb2f8f57f..c887cd0b7b 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -12,7 +11,6 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Configuration; namespace osu.Game.Rulesets.Edit.Layers.Selection { @@ -21,29 +19,18 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public class HitObjectSelectionBox : CompositeDrawable { - public readonly Bindable Selection = new Bindable(); - - /// - /// The s that can be selected through a drag-selection. - /// - public IEnumerable CapturableObjects; - private readonly Container borderMask; private readonly Drawable background; private readonly HandleContainer handles; private Color4 captureFinishedColour; - - private readonly Vector2 startPos; + private RectangleF dragRectangle; /// /// Creates a new . /// - /// The point at which the drag was initiated, in the parent's coordinates. - public HitObjectSelectionBox(Vector2 startPos) + public HitObjectSelectionBox() { - this.startPos = startPos; - InternalChildren = new Drawable[] { new Container @@ -70,8 +57,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both, Alpha = 0, GetDragRectangle = () => dragRectangle, - UpdateDragRectangle = updateDragRectangle, - FinishDrag = FinishCapture + UpdateDragRectangle = SetDragRectangle, + FinishDrag = () => FinishCapture() } }; } @@ -82,49 +69,29 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection captureFinishedColour = colours.Yellow; } - /// - /// The secondary corner of the drag selection box. A rectangle will be fit between the starting position and this value. - /// - public Vector2 DragEndPosition { set => updateDragRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, value.X, value.Y)); } - - private RectangleF dragRectangle; - private void updateDragRectangle(RectangleF rectangle) + public void SetDragRectangle(RectangleF rectangle) { dragRectangle = rectangle; - Position = new Vector2( - Math.Min(rectangle.Left, rectangle.Right), - Math.Min(rectangle.Top, rectangle.Bottom)); + var topLeft = Parent.ToLocalSpace(rectangle.TopLeft); + var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight); - Size = new Vector2( - Math.Max(rectangle.Left, rectangle.Right) - Position.X, - Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); + Position = topLeft; + Size = bottomRight - topLeft; } private readonly List capturedHitObjects = new List(); - /// - /// Processes hitobjects to determine which ones are captured by the drag selection. - /// Captured hitobjects will be enclosed by the drag selection upon . - /// - public void BeginCapture() - { - capturedHitObjects.Clear(); + public bool HasCaptured => capturedHitObjects.Count > 0; - foreach (var obj in CapturableObjects) - { - if (!obj.IsAlive || !obj.IsPresent) - continue; + public void AddCaptured(DrawableHitObject hitObject) => capturedHitObjects.Add(hitObject); - if (ScreenSpaceDrawQuad.Contains(obj.SelectionPoint)) - capturedHitObjects.Add(obj); - } - } + public void ClearCaptured() => capturedHitObjects.Clear(); /// /// Encloses hitobjects captured through in the drag selection box. /// - public void FinishCapture() + public void FinishCapture(bool instant = false) { if (capturedHitObjects.Count == 0) { @@ -145,8 +112,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection topLeft -= new Vector2(5); bottomRight += new Vector2(5); - this.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + this.MoveTo(topLeft, instant ? 0 : 100, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, instant ? 0 : 100, Easing.OutQuint); dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); @@ -156,12 +123,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // Transform into markers to let the user modify the drag selection further. background.Delay(50).FadeOut(200); handles.FadeIn(200); - - Selection.Value = new SelectionInfo - { - Objects = capturedHitObjects, - SelectionQuad = Parent.ToScreenSpace(dragRectangle) - }; } private bool isActive = true; @@ -171,9 +132,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection public override void Hide() { isActive = false; - this.FadeOut(400, Easing.OutQuint).Expire(); - - Selection.Value = null; + this.FadeOut(400, Easing.OutQuint); } } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index 93755d400a..b9ece8759c 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -1,18 +1,18 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit.Layers.Selection { public class SelectionLayer : CompositeDrawable { - public readonly Bindable Selection = new Bindable(); - private readonly Playfield playfield; public SelectionLayer(Playfield playfield) @@ -22,39 +22,80 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private HitObjectSelectionBox selectionBoxBox; + private HitObjectSelectionBox selectionBox; + private HitObjectCapturer capturer; + + [BackgroundDependencyLoader] + private void load() + { + capturer = new HitObjectCapturer(playfield.HitObjects.Objects); + capturer.HitObjectCaptured += hitObjectCaptured; + } + + private void hitObjectCaptured(DrawableHitObject hitObject) => selectionBox.AddCaptured(hitObject); protected override bool OnDragStart(InputState state) { // Hide the previous drag box - we won't be working with it any longer - selectionBoxBox?.Hide(); + selectionBox?.Hide(); + selectionBox?.Expire(); - AddInternal(selectionBoxBox = new HitObjectSelectionBox(ToLocalSpace(state.Mouse.NativeState.Position)) - { - CapturableObjects = playfield.HitObjects.Objects, - }); - - Selection.BindTo(selectionBoxBox.Selection); + AddInternal(selectionBox = new HitObjectSelectionBox()); return true; } protected override bool OnDrag(InputState state) { - selectionBoxBox.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); - selectionBoxBox.BeginCapture(); + var dragPosition = state.Mouse.NativeState.Position; + var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; + + var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); + + selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); + capturer.CaptureQuad(screenSpaceDragQuad); + return true; } protected override bool OnDragEnd(InputState state) { - selectionBoxBox.FinishCapture(); + // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here after OnClick has set the selectionBox to null + // In the case that the user dragged within the click distance out of an object + if (selectionBox == null) + return true; + + selectionBox.FinishCapture(); + + // If there are no hitobjects, remove the selection box + if (!selectionBox.HasCaptured) + { + selectionBox.Expire(); + selectionBox = null; + } + return true; } protected override bool OnClick(InputState state) { - selectionBoxBox?.Hide(); + // We could be coming here without a previous selection box + if (selectionBox == null) + AddInternal(selectionBox = new HitObjectSelectionBox { Position = ToLocalSpace(state.Mouse.NativeState.Position), Alpha = 0 }); + + // If we're coming here with a previous selection, unselect those hitobjects + selectionBox.ClearCaptured(); + if (capturer.CapturePoint(state.Mouse.NativeState.Position)) + { + selectionBox.Alpha = 1; + selectionBox.FinishCapture(true); + } + else + { + selectionBox.Hide(); + selectionBox = null; + } + return true; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bb9925abbc..e1f5eee182 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -332,6 +332,7 @@ +