diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs index 41d1459103..3a19eabe81 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSeekSnapping.cs @@ -175,13 +175,13 @@ public void TestSeekForwardSnappingFromInBetweenBeat() AddAssert("Time = 50", () => Clock.CurrentTime == 50); AddStep("Seek(49.999)", () => Clock.Seek(49.999)); AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); - AddAssert("Time = 50", () => Clock.CurrentTime == 50); + AddAssert("Time = 100", () => Clock.CurrentTime == 100); AddStep("Seek(99)", () => Clock.Seek(99)); AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); AddAssert("Time = 100", () => Clock.CurrentTime == 100); AddStep("Seek(99.999)", () => Clock.Seek(99.999)); AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); - AddAssert("Time = 100", () => Clock.CurrentTime == 100); + AddAssert("Time = 100", () => Clock.CurrentTime == 150); AddStep("Seek(174)", () => Clock.Seek(174)); AddStep("SeekForward, Snap", () => Clock.SeekForward(true)); AddAssert("Time = 175", () => Clock.CurrentTime == 175); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 33c33267a3..fcff672045 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -122,14 +122,19 @@ protected override bool OnMouseDown(MouseDownEvent e) return e.Button == MouseButton.Left; } + private SelectionBlueprint clickedBlueprint; + protected override bool OnClick(ClickEvent e) { if (e.Button == MouseButton.Right) return false; + // store for double-click handling + clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + // Deselection should only occur if no selected blueprints are hovered // A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection - if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered)) + if (endClickSelection() || clickedBlueprint != null) return true; deselectAll(); @@ -141,9 +146,8 @@ protected override bool OnDoubleClick(DoubleClickEvent e) if (e.Button == MouseButton.Right) return false; - SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); - - if (clickedBlueprint == null) + // ensure the blueprint which was hovered for the first click is still the hovered blueprint. + if (clickedBlueprint == null || selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint) return false; editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9f61589c36..d92f3922c3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -275,11 +275,22 @@ protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnScroll(ScrollEvent e) { - scrollAccumulation += (e.ScrollDelta.X + e.ScrollDelta.Y) * (e.IsPrecise ? 0.1 : 1); + const double precision = 1; - const int precision = 1; + double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y; - while (Math.Abs(scrollAccumulation) > precision) + double scrollDirection = Math.Sign(scrollComponent); + + // this is a special case to handle the "pivot" scenario. + // if we are precise scrolling in one direction then change our mind and scroll backwards, + // the existing accumulation should be applied in the inverse direction to maintain responsiveness. + if (Math.Sign(scrollAccumulation) != scrollDirection) + scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation)); + + scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1); + + // because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off. + while (Math.Abs(scrollAccumulation) >= precision) { if (scrollAccumulation > 0) seek(e, -1); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index dd934c10cd..d4d0feb813 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -118,9 +118,14 @@ private void seek(int direction, bool snapped, double amount = 1) seekTime = timingPoint.Time + closestBeat * seekAmount; + // limit forward seeking to only up to the next timing point's start time. + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + if (seekTime > nextTimingPoint?.Time) + seekTime = nextTimingPoint.Time; + // Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this. // Instead, we'll go to the next beat in the direction when this is the case - if (Precision.AlmostEquals(current, seekTime)) + if (Precision.AlmostEquals(current, seekTime, 0.5f)) { closestBeat += direction > 0 ? 1 : -1; seekTime = timingPoint.Time + closestBeat * seekAmount; @@ -129,10 +134,6 @@ private void seek(int direction, bool snapped, double amount = 1) if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); - if (seekTime > nextTimingPoint?.Time) - seekTime = nextTimingPoint.Time; - // Ensure the sought point is within the boundaries seekTime = Math.Clamp(seekTime, 0, TrackLength); SeekTo(seekTime);