From 3e8a13bfbfba8ab70203435e0ed51c4e6aca6907 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Fri, 16 Jul 2021 16:16:34 +0200 Subject: [PATCH 01/10] Allow interacting with timeline objects outside of drawable bounds --- .../Timeline/TimelineBlueprintContainer.cs | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index a642768574..cb733ab17d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -31,18 +31,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(CanBeNull = true)] private Timeline timeline { get; set; } - [Resolved] - private OsuColour colours { get; set; } - private DragEvent lastDragEvent; private Bindable placement; private SelectionBlueprint placementBlueprint; - private SelectableAreaBackground backgroundBox; + // We want children to be able to be clicked and dragged + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - // we only care about checking vertical validity. - // this allows selecting and dragging selections before time=0. - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + // This drawable itself should still check whether the mouse is over it + private bool shouldHandleInputAt(Vector2 screenSpacePos) { float localY = ToLocalSpace(screenSpacePos).Y; return DrawRectangle.Top <= localY && DrawRectangle.Bottom >= localY; @@ -61,7 +58,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - AddInternal(backgroundBox = new SelectableAreaBackground + AddInternal(new SelectableAreaBackground { Colour = Color4.Black, Depth = float.MaxValue, @@ -100,16 +97,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - protected override bool OnHover(HoverEvent e) + protected override bool OnDragStart(DragStartEvent e) { - backgroundBox.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); - return base.OnHover(e); - } + if (!shouldHandleInputAt(e.ScreenSpaceMouseDownPosition)) + return false; - protected override void OnHoverLost(HoverLostEvent e) - { - backgroundBox.FadeColour(Color4.Black, 600, Easing.OutQuint); - base.OnHoverLost(e); + return base.OnDragStart(e); } protected override void OnDrag(DragEvent e) @@ -184,7 +177,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { return new TimelineHitObjectBlueprint(item) { - OnDragHandled = handleScrollViaDrag + OnDragHandled = handleScrollViaDrag, }; } @@ -212,6 +205,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class SelectableAreaBackground : CompositeDrawable { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + float localY = ToLocalSpace(screenSpacePos).Y; + return DrawRectangle.Top <= localY && DrawRectangle.Bottom >= localY; + } + [BackgroundDependencyLoader] private void load() { @@ -235,6 +234,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }); } + + [Resolved] + private OsuColour colours { get; set; } + + protected override bool OnHover(HoverEvent e) + { + this.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeColour(Color4.Black, 600, Easing.OutQuint); + base.OnHoverLost(e); + } } internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler From e35cff99c79490faa9d7789aa84031763c6dc4e3 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Fri, 16 Jul 2021 17:21:43 +0200 Subject: [PATCH 02/10] Pass on mouseDown input to timeline if no selection modification is made with that input --- .../Timeline/TimelineBlueprintContainer.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index cb733ab17d..abd7f5923c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -97,6 +97,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; + protected override bool OnMouseDown(MouseDownEvent e) + { + int selectionCount = SelectedItems.Count; + + // We let BlueprintContainer attempt a HitObject selection + // If it fails, we'll pass it this input back to the timeline so it can be dragged + // We know it failed if the selection count is unchanged after the selection attempt + if (base.OnMouseDown(e) && selectionCount != SelectedItems.Count) + return true; + + return false; + } + protected override bool OnDragStart(DragStartEvent e) { if (!shouldHandleInputAt(e.ScreenSpaceMouseDownPosition)) From ee220feecf09781876973366ea136b22c6d5b809 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 18 Jul 2021 16:04:23 +0200 Subject: [PATCH 03/10] Avoid using guesses to determine whether inputs blocked --- .../Compose/Components/BlueprintContainer.cs | 12 ++++--- .../Timeline/TimelineBlueprintContainer.cs | 33 +++++-------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 185f029d14..06cbf7c2e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -110,9 +110,9 @@ namespace osu.Game.Screens.Edit.Compose.Components bool selectionPerformed = performMouseDownActions(e); // even if a selection didn't occur, a drag event may still move the selection. - prepareSelectionMovement(); + bool movementPossible = prepareSelectionMovement(); - return selectionPerformed || e.Button == MouseButton.Left; + return selectionPerformed || movementPossible; } protected SelectionBlueprint ClickedBlueprint { get; private set; } @@ -427,19 +427,21 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Attempts to begin the movement of any selected blueprints. /// - private void prepareSelectionMovement() + /// Whether a movement is possible. + private bool prepareSelectionMovement() { if (!SelectionHandler.SelectedBlueprints.Any()) - return; + return false; // Any selected blueprint that is hovered can begin the movement of the group, however only the first item (according to SortForMovement) is used for movement. // A special case is added for when a click selection occurred before the drag if (!clickSelectionBegan && !SelectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) - return; + return false; // Movement is tracked from the blueprint of the earliest item, since it only makes sense to distance snap from that item movementBlueprints = SortForMovement(SelectionHandler.SelectedBlueprints).ToArray(); movementBlueprintOriginalPositions = movementBlueprints.Select(m => m.ScreenSpaceSelectionPoint).ToArray(); + return true; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index abd7f5923c..ae33eaa355 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -35,15 +35,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable placement; private SelectionBlueprint placementBlueprint; - // We want children to be able to be clicked and dragged - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + private SelectableAreaBackground backgroundBox; - // This drawable itself should still check whether the mouse is over it - private bool shouldHandleInputAt(Vector2 screenSpacePos) - { - float localY = ToLocalSpace(screenSpacePos).Y; - return DrawRectangle.Top <= localY && DrawRectangle.Bottom >= localY; - } + // We want children to be able to be clicked and dragged, regardless of this drawable's size + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public TimelineBlueprintContainer(HitObjectComposer composer) : base(composer) @@ -58,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - AddInternal(new SelectableAreaBackground + AddInternal(backgroundBox = new SelectableAreaBackground { Colour = Color4.Black, Depth = float.MaxValue, @@ -97,25 +92,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - protected override bool OnMouseDown(MouseDownEvent e) - { - int selectionCount = SelectedItems.Count; - - // We let BlueprintContainer attempt a HitObject selection - // If it fails, we'll pass it this input back to the timeline so it can be dragged - // We know it failed if the selection count is unchanged after the selection attempt - if (base.OnMouseDown(e) && selectionCount != SelectedItems.Count) - return true; - - return false; - } - protected override bool OnDragStart(DragStartEvent e) { - if (!shouldHandleInputAt(e.ScreenSpaceMouseDownPosition)) - return false; + // We should only allow BlueprintContainer to create a drag box if the mouse is within selection bounds + if (backgroundBox.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition)) + return base.OnDragStart(e); - return base.OnDragStart(e); + return false; } protected override void OnDrag(DragEvent e) From 2e2a2bdd99bda1065e7f79f3064d6dcf40cc3dba Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 18 Jul 2021 17:20:30 +0200 Subject: [PATCH 04/10] Allow moving timeline selection when mousedown event is outside of blueprint container --- .../Timeline/TimelineBlueprintContainer.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index ae33eaa355..8e0b39513a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable placement; private SelectionBlueprint placementBlueprint; - private SelectableAreaBackground backgroundBox; - // We want children to be able to be clicked and dragged, regardless of this drawable's size public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -53,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { - AddInternal(backgroundBox = new SelectableAreaBackground + AddInternal(new SelectableAreaBackground { Colour = Color4.Black, Depth = float.MaxValue, @@ -92,15 +90,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override Container> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; - protected override bool OnDragStart(DragStartEvent e) - { - // We should only allow BlueprintContainer to create a drag box if the mouse is within selection bounds - if (backgroundBox.ReceivePositionalInputAt(e.ScreenSpaceMouseDownPosition)) - return base.OnDragStart(e); - - return false; - } - protected override void OnDrag(DragEvent e) { handleScrollViaDrag(e); @@ -319,6 +308,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool HandleDrag(MouseButtonEvent e) { + // The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds. + if (DrawRectangle.Top > e.MouseDownPosition.Y || DrawRectangle.Bottom < e.MouseDownPosition.Y) + return false; + selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; // only calculate end when a transition is not in progress to avoid bouncing. From bfec87b0829294c59500c1e4cc204686c2bc7371 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 20 Jul 2021 22:25:17 +0200 Subject: [PATCH 05/10] Let TimelineBlueprintContainer only accept positional input within timeline quad --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 8e0b39513a..69db191cce 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable placement; private SelectionBlueprint placementBlueprint; - // We want children to be able to be clicked and dragged, regardless of this drawable's size - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + // We want children within the timeline to be interactable + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => timeline.ScreenSpaceDrawQuad.Contains(screenSpacePos); public TimelineBlueprintContainer(HitObjectComposer composer) : base(composer) From a8cf6a6854231bc2f7fbeffee45d97460c2dd812 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 20 Jul 2021 23:00:58 +0200 Subject: [PATCH 06/10] Fix slight Y position offset in HandleDrag --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 69db191cce..a5210132a3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -309,7 +309,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public override bool HandleDrag(MouseButtonEvent e) { // The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds. - if (DrawRectangle.Top > e.MouseDownPosition.Y || DrawRectangle.Bottom < e.MouseDownPosition.Y) + float localY = ToLocalSpace(e.ScreenSpaceMouseDownPosition).Y; + if (DrawRectangle.Top > localY || DrawRectangle.Bottom < localY) return false; selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; From f85ff40a6b3eddcc4e9a0ae21853eed059759616 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 21 Jul 2021 11:47:21 +0200 Subject: [PATCH 07/10] Add back LeftMouse button check --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 06cbf7c2e0..a24084aa05 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // even if a selection didn't occur, a drag event may still move the selection. bool movementPossible = prepareSelectionMovement(); - return selectionPerformed || movementPossible; + return selectionPerformed || (e.Button == MouseButton.Left && movementPossible); } protected SelectionBlueprint ClickedBlueprint { get; private set; } From 9d43ca122fc52372d0c50ebe84789095def45d6b Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 21 Jul 2021 12:04:09 +0200 Subject: [PATCH 08/10] Allow context menus to be triggered as well --- .../Components/Timeline/TimelineBlueprintContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index a5210132a3..354311b7ff 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -238,6 +238,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler { + [Resolved] + private Timeline timeline { get; set; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => timeline.ScreenSpaceDrawQuad.Contains(screenSpacePos); + // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; From b5cc9010de97d5d81d608faf155d8fcd1a864b93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Jul 2021 15:39:01 +0900 Subject: [PATCH 09/10] Move resolved property to top of class --- .../Components/Timeline/TimelineBlueprintContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 354311b7ff..184bec7d44 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -190,6 +190,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private class SelectableAreaBackground : CompositeDrawable { + [Resolved] + private OsuColour colours { get; set; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) { float localY = ToLocalSpace(screenSpacePos).Y; @@ -220,9 +223,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } - [Resolved] - private OsuColour colours { get; set; } - protected override bool OnHover(HoverEvent e) { this.FadeColour(colours.BlueLighter, 120, Easing.OutQuint); From 957a0686ed7625779f4db162d043b2ebbb2e0218 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Jul 2021 15:48:07 +0900 Subject: [PATCH 10/10] Split out nested classes from `TimelineBlueprintContainer` They got too big. --- .../Timeline/TimelineBlueprintContainer.cs | 121 ------------------ .../Components/Timeline/TimelineDragBox.cs | 79 ++++++++++++ .../Timeline/TimelineSelectionHandler.cs | 65 ++++++++++ 3 files changed, 144 insertions(+), 121 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 184bec7d44..73c38ba23f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -13,11 +13,9 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -236,125 +234,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler - { - [Resolved] - private Timeline timeline { get; set; } - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => timeline.ScreenSpaceDrawQuad.Contains(screenSpacePos); - - // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation - public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; - - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.EditorNudgeLeft: - nudgeSelection(-1); - return true; - - case GlobalAction.EditorNudgeRight: - nudgeSelection(1); - return true; - } - - return false; - } - - public void OnReleased(GlobalAction action) - { - } - - /// - /// Nudge the current selection by the specified multiple of beat divisor lengths, - /// based on the timing at the first object in the selection. - /// - /// The direction and count of beat divisor lengths to adjust. - private void nudgeSelection(int amount) - { - var selected = EditorBeatmap.SelectedHitObjects; - - if (selected.Count == 0) - return; - - var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime); - double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount; - - EditorBeatmap.PerformOnSelection(h => - { - h.StartTime += adjustment; - EditorBeatmap.Update(h); - }); - } - } - - private class TimelineDragBox : DragBox - { - // the following values hold the start and end X positions of the drag box in the timeline's local space, - // but with zoom unapplied in order to be able to compensate for positional changes - // while the timeline is being zoomed in/out. - private float? selectionStart; - private float selectionEnd; - - [Resolved] - private Timeline timeline { get; set; } - - public TimelineDragBox(Action performSelect) - : base(performSelect) - { - } - - protected override Drawable CreateBox() => new Box - { - RelativeSizeAxes = Axes.Y, - Alpha = 0.3f - }; - - public override bool HandleDrag(MouseButtonEvent e) - { - // The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds. - float localY = ToLocalSpace(e.ScreenSpaceMouseDownPosition).Y; - if (DrawRectangle.Top > localY || DrawRectangle.Bottom < localY) - return false; - - selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; - - // only calculate end when a transition is not in progress to avoid bouncing. - if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom)) - selectionEnd = e.MousePosition.X / timeline.CurrentZoom; - - updateDragBoxPosition(); - return true; - } - - private void updateDragBoxPosition() - { - if (selectionStart == null) - return; - - float rescaledStart = selectionStart.Value * timeline.CurrentZoom; - float rescaledEnd = selectionEnd * timeline.CurrentZoom; - - Box.X = Math.Min(rescaledStart, rescaledEnd); - Box.Width = Math.Abs(rescaledStart - rescaledEnd); - - var boxScreenRect = Box.ScreenSpaceDrawQuad.AABBFloat; - - // we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment. - boxScreenRect.Y -= boxScreenRect.Height; - boxScreenRect.Height *= 2; - - PerformSelection?.Invoke(boxScreenRect); - } - - public override void Hide() - { - base.Hide(); - selectionStart = null; - } - } - protected class TimelineSelectionBlueprintContainer : Container> { protected override Container> Content { get; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs new file mode 100644 index 0000000000..8aad8aa6dc --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineDragBox.cs @@ -0,0 +1,79 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimelineDragBox : DragBox + { + // the following values hold the start and end X positions of the drag box in the timeline's local space, + // but with zoom unapplied in order to be able to compensate for positional changes + // while the timeline is being zoomed in/out. + private float? selectionStart; + private float selectionEnd; + + [Resolved] + private Timeline timeline { get; set; } + + public TimelineDragBox(Action performSelect) + : base(performSelect) + { + } + + protected override Drawable CreateBox() => new Box + { + RelativeSizeAxes = Axes.Y, + Alpha = 0.3f + }; + + public override bool HandleDrag(MouseButtonEvent e) + { + // The dragbox should only be active if the mouseDownPosition.Y is within this drawable's bounds. + float localY = ToLocalSpace(e.ScreenSpaceMouseDownPosition).Y; + if (DrawRectangle.Top > localY || DrawRectangle.Bottom < localY) + return false; + + selectionStart ??= e.MouseDownPosition.X / timeline.CurrentZoom; + + // only calculate end when a transition is not in progress to avoid bouncing. + if (Precision.AlmostEquals(timeline.CurrentZoom, timeline.Zoom)) + selectionEnd = e.MousePosition.X / timeline.CurrentZoom; + + updateDragBoxPosition(); + return true; + } + + private void updateDragBoxPosition() + { + if (selectionStart == null) + return; + + float rescaledStart = selectionStart.Value * timeline.CurrentZoom; + float rescaledEnd = selectionEnd * timeline.CurrentZoom; + + Box.X = Math.Min(rescaledStart, rescaledEnd); + Box.Width = Math.Abs(rescaledStart - rescaledEnd); + + var boxScreenRect = Box.ScreenSpaceDrawQuad.AABBFloat; + + // we don't care about where the hitobjects are vertically. in cases like stacking display, they may be outside the box without this adjustment. + boxScreenRect.Y -= boxScreenRect.Height; + boxScreenRect.Height *= 2; + + PerformSelection?.Invoke(boxScreenRect); + } + + public override void Hide() + { + base.Hide(); + selectionStart = null; + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs new file mode 100644 index 0000000000..354013a5fd --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + internal class TimelineSelectionHandler : EditorSelectionHandler, IKeyBindingHandler + { + [Resolved] + private Timeline timeline { get; set; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => timeline.ScreenSpaceDrawQuad.Contains(screenSpacePos); + + // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation + public override bool HandleMovement(MoveSelectionEvent moveEvent) => true; + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.EditorNudgeLeft: + nudgeSelection(-1); + return true; + + case GlobalAction.EditorNudgeRight: + nudgeSelection(1); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } + + /// + /// Nudge the current selection by the specified multiple of beat divisor lengths, + /// based on the timing at the first object in the selection. + /// + /// The direction and count of beat divisor lengths to adjust. + private void nudgeSelection(int amount) + { + var selected = EditorBeatmap.SelectedHitObjects; + + if (selected.Count == 0) + return; + + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(selected.First().StartTime); + double adjustment = timingPoint.BeatLength / EditorBeatmap.BeatDivisor * amount; + + EditorBeatmap.PerformOnSelection(h => + { + h.StartTime += adjustment; + EditorBeatmap.Update(h); + }); + } + } +}