From dffc58c5fae9e18fc96af3b8128ce3a7b2d06226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Feb 2020 12:23:02 +0900 Subject: [PATCH 01/12] Add blueprint type to timeline test --- .../Editor/TestSceneTimelineBlueprintContainer.cs | 13 +++++++++++++ .../Visual/Editor/TestSceneTimingScreen.cs | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index 3c75fd5310..4d8f877575 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs @@ -1,6 +1,8 @@ // 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -10,6 +12,17 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestSceneTimelineBlueprintContainer : TimelineTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TimelineHitObjectBlueprint), + }; + public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(); + + protected override void LoadComplete() + { + base.LoadComplete(); + Clock.Seek(10000); + } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index adfed9a299..ae09a7fa47 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -27,7 +27,12 @@ namespace osu.Game.Tests.Visual.Editor }; [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + private readonly EditorBeatmap editorBeatmap; + + public TestSceneTimingScreen() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } [BackgroundDependencyLoader] private void load() From cd6902a312480e53a7f49d1fa264ef158750d4cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:12:26 +0900 Subject: [PATCH 02/12] Make EndTime and RepeatCount settable --- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 6 +++++- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 6 +++++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 6 +++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +++++++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 6 +++++- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 6 +++++- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 7 ++++++- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 5 ++++- osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 2 +- 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 267e6d12c7..0c754412e5 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -36,7 +36,11 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a4ed966abb..4d68bf592c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -110,7 +110,11 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Path.Distance / Velocity; + set => throw new System.NotImplementedException(); + } public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index bdba813eed..86d3d2b2b0 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasEndTime { - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } private double duration; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index fe65ab78d1..bab7e6dbee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -18,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Path.Distance / Velocity; + set => throw new System.NotImplementedException(); + } + public double Duration => EndTime - StartTime; private readonly Cached endPositionCache = new Cached(); @@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects set { repeatCount = value; - endPositionCache.Invalidate(); + updateNestedPositions(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 8956ca9c19..aacd78f176 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects /// private const float base_distance = 100; - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index e60984596d..2f06066a16 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class Swell : TaikoHitObject, IHasEndTime { - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 8d523022d6..b5cd9be245 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,7 +26,12 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Distance / Velocity; + set => throw new System.NotImplementedException(); + } + public double Duration => EndTime - StartTime; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 516f1002a4..bc7103c60d 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using Newtonsoft.Json; + namespace osu.Game.Rulesets.Objects.Types { /// @@ -11,7 +13,8 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - double EndTime { get; } + [JsonIgnore] + double EndTime { get; set; } /// /// The duration of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index b22752e902..256b1f3963 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The amount of times the HitObject repeats. /// - int RepeatCount { get; } + int RepeatCount { get; set; } /// /// The samples to be played when each node of the is hit.
From d04cc0123df7c7c7d3a476a03a27f93dd79257f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:16:37 +0900 Subject: [PATCH 03/12] Initial implementation of timeline blueprint dragbars --- .../Compose/Components/Timeline/Timeline.cs | 13 ++- .../Timeline/TimelineHitObjectBlueprint.cs | 99 ++++++++++++++----- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a33040f400..0475e68e42 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -179,11 +179,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) - { - var targetTime = (position.X / Content.DrawWidth) * track.Length; - return (position, beatSnapProvider.SnapTime(targetTime)); - } + public double GetTimeFromScreenSpacePosition(Vector2 position) + => getTimeFromPosition(Content.ToLocalSpace(position)); + + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => + (position, beatSnapProvider.SnapTime(getTimeFromPosition(position))); + + private double getTimeFromPosition(Vector2 localPosition) => + (localPosition.X / Content.DrawWidth) * track.Length; public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 2ed5471444..2ac35534f4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -24,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [UsedImplicitly] private readonly Bindable startTime; - public const float THICKNESS = 3; + public const float THICKNESS = 5; private const float circle_size = 16; @@ -44,25 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - if (hitObject is IHasEndTime) - { - AddInternal(extensionBar = new Container - { - CornerRadius = 2, - Masking = true, - Size = new Vector2(1, THICKNESS), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.Black, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }); - } - AddInternal(circle = new Circle { Size = new Vector2(circle_size), @@ -74,6 +57,78 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline BorderColour = Color4.Black, BorderThickness = THICKNESS, }); + + if (hitObject is IHasEndTime) + { + AddRangeInternal(new Drawable[] + { + extensionBar = new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, THICKNESS), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }, + new DragBar(hitObject), + }); + } + } + + public class DragBar : CompositeDrawable + { + private readonly HitObject hitObject; + + [Resolved] + private Timeline timeline { get; set; } + + public DragBar(HitObject hitObject) + { + this.hitObject = hitObject; + + CornerRadius = 2; + Masking = true; + Size = new Vector2(THICKNESS, 1.5f); + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition); + + switch (hitObject) + { + case IHasRepeats repeatHitObject: + repeatHitObject.RepeatCount = (int)((time - hitObject.StartTime) / (repeatHitObject.Duration / repeatHitObject.RepeatCount)); + break; + + case IHasEndTime endTimeHitObject: + endTimeHitObject.EndTime = time; + break; + } + + beatmap.UpdateHitObject(hitObject); + } } protected override void Update() @@ -88,14 +143,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { circle.BorderColour = Color4.Orange; if (extensionBar != null) - extensionBar.Colour = Color4.Orange; + extensionBar.BorderColour = Color4.Orange; } protected override void OnDeselected() { circle.BorderColour = Color4.Black; if (extensionBar != null) - extensionBar.Colour = Color4.Black; + extensionBar.BorderColour = Color4.Black; } public override Quad SelectionQuad From 09273d1da97a5b2ffe75fc9185a0c43161fdcb7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 18:14:44 +0900 Subject: [PATCH 04/12] Fix test scene not correctly building a playable beatmap --- .../Visual/Editor/TimelineTestScene.cs | 5 ++-- osu.Game.Tests/WaveformTestBeatmap.cs | 27 ++++++++++++++----- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 4 ++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs index 40c0fedc9e..7081eb3af5 100644 --- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs @@ -13,7 +13,6 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -38,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editor { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + + var editorBeatmap = new EditorBeatmap(playable); Dependencies.Cache(editorBeatmap); Dependencies.CacheAs(editorBeatmap); diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index b7d7bb1ee1..df6394ed34 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -11,6 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets.Catch; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -20,11 +22,18 @@ namespace osu.Game.Tests ///
public class WaveformTestBeatmap : WorkingBeatmap { + private readonly Beatmap beatmap; private readonly ITrackStore trackStore; public WaveformTestBeatmap(AudioManager audioManager) - : base(new BeatmapInfo(), audioManager) + : this(audioManager, new WaveformBeatmap()) { + } + + public WaveformTestBeatmap(AudioManager audioManager, Beatmap beatmap) + : base(beatmap.BeatmapInfo, audioManager) + { + this.beatmap = beatmap; trackStore = audioManager.GetTrackStore(getZipReader()); } @@ -34,11 +43,11 @@ namespace osu.Game.Tests trackStore?.Dispose(); } - private Stream getStream() => TestResources.GetTestBeatmapStream(); + private static Stream getStream() => TestResources.GetTestBeatmapStream(); - private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); + private static ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); - protected override IBeatmap GetBeatmap() => createTestBeatmap(); + protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; @@ -57,10 +66,16 @@ namespace osu.Game.Tests } } - private Beatmap createTestBeatmap() + private class WaveformBeatmap : TestBeatmap { - using (var reader = getZipReader()) + public WaveformBeatmap() + : base(new CatchRuleset().RulesetInfo) { + } + + protected override Beatmap CreateBeatmap() + { + using (var reader = getZipReader()) using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))) using (var beatmapReader = new LineBufferedReader(beatmapStream)) return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index d6f92ba086..96e3c037a3 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Beatmaps { public TestBeatmap(RulesetInfo ruleset) { - var baseBeatmap = createTestBeatmap(); + var baseBeatmap = CreateBeatmap(); BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; @@ -37,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps }; } + protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); + private static Beatmap createTestBeatmap() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) From d56accaef1ab193f33d214a16e53a56a8e22be49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:32:51 +0900 Subject: [PATCH 05/12] Disallow negative / zero repeat counts (and fix off-by-one) --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 2ac35534f4..b2da1577d0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -119,7 +119,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline switch (hitObject) { case IHasRepeats repeatHitObject: - repeatHitObject.RepeatCount = (int)((time - hitObject.StartTime) / (repeatHitObject.Duration / repeatHitObject.RepeatCount)); + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1; + + if (proposedCount == repeatHitObject.RepeatCount || proposedCount < 0) + return; + + repeatHitObject.RepeatCount = proposedCount; break; case IHasEndTime endTimeHitObject: From cef45afbc85eb24bad74f44d1e8d4c8d16913b86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:33:04 +0900 Subject: [PATCH 06/12] Add a simple hover state --- .../Timeline/TimelineHitObjectBlueprint.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b2da1577d0..8a6dd40bde 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Container extensionBar; + protected override bool ShouldBeConsideredForInput(Drawable child) => true; + [UsedImplicitly] private readonly Bindable startTime; @@ -105,6 +107,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; } + protected override bool OnHover(HoverEvent e) + { + updateState(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private bool hasMouseDown; + + protected override bool OnMouseDown(MouseDownEvent e) + { + hasMouseDown = true; + updateState(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + hasMouseDown = false; + updateState(); + base.OnMouseUp(e); + } + + private void updateState() + { + if (IsHovered || hasMouseDown) + Colour = Color4.Orange; + else + { + Colour = Color4.White; + } + } + protected override bool OnDragStart(DragStartEvent e) => true; [Resolved] From 3d42973764434fbfe5359996e9edb270a56c759d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:58:35 +0900 Subject: [PATCH 07/12] Allow scrolling via drag while dragging a hold note handle --- .../Timeline/TimelineBlueprintContainer.cs | 40 ++++++++++++------- .../Timeline/TimelineHitObjectBlueprint.cs | 16 +++++++- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 3b9cb1df24..9f3d776e5c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -49,20 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnDrag(DragEvent e) { - if (timeline != null) - { - var timelineQuad = timeline.ScreenSpaceDrawQuad; - var mouseX = e.ScreenSpaceMousePosition.X; - - // scroll if in a drag and dragging outside visible extents - if (mouseX > timelineQuad.TopRight.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < timelineQuad.TopLeft.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); - } + handleScrollViaDrag(e); base.OnDrag(e); - lastDragEvent = e; } protected override void OnDragEnd(DragEndEvent e) @@ -74,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void Update() { // trigger every frame so drags continue to update selection while playback is scrolling the timeline. - if (IsDragged) + if (lastDragEvent != null) OnDrag(lastDragEvent); base.Update(); @@ -82,10 +71,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject) + { + OnDragHandled = handleScrollViaDrag + }; protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + private void handleScrollViaDrag(DragEvent e) + { + lastDragEvent = e; + + if (lastDragEvent == null) + return; + + if (timeline != null) + { + var timelineQuad = timeline.ScreenSpaceDrawQuad; + var mouseX = e.ScreenSpaceMousePosition.X; + + // scroll if in a drag and dragging outside visible extents + if (mouseX > timelineQuad.TopRight.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < timelineQuad.TopLeft.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + } + } + internal class TimelineSelectionHandler : SelectionHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 8a6dd40bde..3de1cd3a4c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -1,6 +1,7 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -28,6 +29,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [UsedImplicitly] private readonly Bindable startTime; + public Action OnDragHandled; + public const float THICKNESS = 5; private const float circle_size = 16; @@ -78,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both, } }, - new DragBar(hitObject), + new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) } }); } } @@ -90,6 +93,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Timeline timeline { get; set; } + public Action OnDragHandled; + public DragBar(HitObject hitObject) { this.hitObject = hitObject; @@ -155,6 +160,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.OnDrag(e); + OnDragHandled?.Invoke(e); + var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition); switch (hitObject) @@ -177,6 +184,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.UpdateHitObject(hitObject); } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + + OnDragHandled?.Invoke(null); + } } protected override void Update() From 98ab1f986272e89c0d092ec5cd01b726b74a4998 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 16:35:07 +0900 Subject: [PATCH 08/12] Fix negative spinners --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 3de1cd3a4c..5a15cb42fa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -156,6 +156,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorBeatmap beatmap { get; set; } + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } + protected override void OnDrag(DragEvent e) { base.OnDrag(e); @@ -178,7 +181,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case IHasEndTime endTimeHitObject: - endTimeHitObject.EndTime = time; + var snappedTime = beatSnapProvider.SnapTime(time); + + if (endTimeHitObject.EndTime == snappedTime || snappedTime <= hitObject.StartTime) + return; + + endTimeHitObject.EndTime = snappedTime; break; } From f5edad16e67d3a9151dd0118f11d5b48bf4c2e91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 19:43:13 +0900 Subject: [PATCH 09/12] Improve visuals --- .../Timeline/TimelineHitObjectBlueprint.cs | 184 ++++++++++++------ 1 file changed, 125 insertions(+), 59 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 5a15cb42fa..b46a373818 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -2,14 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -22,8 +25,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly Circle circle; - private readonly Container extensionBar; - protected override bool ShouldBeConsideredForInput(Drawable child) => true; [UsedImplicitly] @@ -31,11 +32,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action OnDragHandled; - public const float THICKNESS = 5; + private readonly DragBar dragBar; + + private readonly List shadowComponents = new List(); + + private const float thickness = 5; + + private const float shadow_radius = 5; private const float circle_size = 16; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) || + circle.ReceivePositionalInputAt(screenSpacePos) || + dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) @@ -51,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - AddInternal(circle = new Circle + circle = new Circle { Size = new Vector2(circle_size), Anchor = Anchor.CentreLeft, @@ -59,34 +69,126 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X, AlwaysPresent = true, Colour = Color4.White, - BorderColour = Color4.Black, - BorderThickness = THICKNESS, - }); + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = Color4.Black + }, + }; + + shadowComponents.Add(circle); if (hitObject is IHasEndTime) { + DragBar dragBarUnderlay; + Container extensionBar; + AddRangeInternal(new Drawable[] { extensionBar = new Container { - CornerRadius = 2, Masking = true, - Size = new Vector2(1, THICKNESS), + Size = new Vector2(1, thickness), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, RelativeSizeAxes = Axes.X, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = Color4.Black + }, Child = new Box { RelativeSizeAxes = Axes.Both, } }, - new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) } + circle, + // only used for drawing the shadow + dragBarUnderlay = new DragBar(null), + // cover up the shadow on the join + new Box + { + Height = thickness, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + }, + dragBar = new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) }, }); + + shadowComponents.Add(dragBarUnderlay); + shadowComponents.Add(extensionBar); + } + else + { + AddInternal(circle); + } + + updateShadows(); + } + + protected override void Update() + { + base.Update(); + + // no bindable so we perform this every update + Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + } + + protected override void OnSelected() + { + updateShadows(); + } + + private void updateShadows() + { + foreach (var s in shadowComponents) + { + if (State == SelectionState.Selected) + { + s.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius / 2, + Colour = Color4.Orange, + }; + } + else + { + s.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = State == SelectionState.Selected ? Color4.Orange : Color4.Black + }; + } } } - public class DragBar : CompositeDrawable + protected override void OnDeselected() + { + updateShadows(); + } + + public override Quad SelectionQuad + { + get + { + // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. + var leftQuad = circle.ScreenSpaceDrawQuad; + var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad; + + return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight), + leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight)); + } + } + + public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; + + public class DragBar : Container { private readonly HitObject hitObject; @@ -95,20 +197,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action OnDragHandled; + public override bool HandlePositionalInput => hitObject != null; + public DragBar(HitObject hitObject) { this.hitObject = hitObject; CornerRadius = 2; Masking = true; - Size = new Vector2(THICKNESS, 1.5f); + Size = new Vector2(5, 1); Anchor = Anchor.CentreRight; - Origin = Anchor.CentreRight; + Origin = Anchor.Centre; RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - InternalChild = new Box + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + } }; } @@ -143,12 +251,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateState() { - if (IsHovered || hasMouseDown) - Colour = Color4.Orange; - else - { - Colour = Color4.White; - } + Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White; } protected override bool OnDragStart(DragStartEvent e) => true; @@ -200,42 +303,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled?.Invoke(null); } } - - protected override void Update() - { - base.Update(); - - // no bindable so we perform this every update - Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); - } - - protected override void OnSelected() - { - circle.BorderColour = Color4.Orange; - if (extensionBar != null) - extensionBar.BorderColour = Color4.Orange; - } - - protected override void OnDeselected() - { - circle.BorderColour = Color4.Black; - if (extensionBar != null) - extensionBar.BorderColour = Color4.Black; - } - - public override Quad SelectionQuad - { - get - { - // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. - var circleQuad = circle.ScreenSpaceDrawQuad; - var actualQuad = ScreenSpaceDrawQuad; - - return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), - circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); - } - } - - public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; } } From daf5fa9da4ade1124f75a250e0c1c8964372fe16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:16:32 +0900 Subject: [PATCH 10/12] Throw NotSupportedException instead --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 4d68bf592c..b014b32305 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double EndTime { get => StartTime + this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index bab7e6dbee..95fb6d9d48 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime { get => StartTime + this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double Duration => EndTime - StartTime; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index b5cd9be245..53cdf457c4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double EndTime { get => StartTime + this.SpanCount() * Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double Duration => EndTime - StartTime; From c138e3907e13543a95a86a25021d5f35b56af285 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Feb 2020 14:35:45 +0900 Subject: [PATCH 11/12] Move methods below ctor --- .../Timeline/TimelineHitObjectBlueprint.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b46a373818..5225a4299e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly Circle circle; - protected override bool ShouldBeConsideredForInput(Drawable child) => true; - [UsedImplicitly] private readonly Bindable startTime; @@ -42,11 +40,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float circle_size = 16; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - base.ReceivePositionalInputAt(screenSpacePos) || - circle.ReceivePositionalInputAt(screenSpacePos) || - dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; - public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) { @@ -138,6 +131,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); } + protected override bool ShouldBeConsideredForInput(Drawable child) => true; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) || + circle.ReceivePositionalInputAt(screenSpacePos) || + dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; + protected override void OnSelected() { updateShadows(); From 6ae0efa40d350521d28720fccd050ce7502fb8e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Feb 2020 14:47:43 +0900 Subject: [PATCH 12/12] Fix adjustment not working when dragged before object --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 5225a4299e..8f12c2f0ed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -275,18 +275,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasRepeats repeatHitObject: // find the number of repeats which can fit in the requested time. var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1; + var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount || proposedCount < 0) + if (proposedCount == repeatHitObject.RepeatCount) return; repeatHitObject.RepeatCount = proposedCount; break; case IHasEndTime endTimeHitObject: - var snappedTime = beatSnapProvider.SnapTime(time); + var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime || snappedTime <= hitObject.StartTime) + if (endTimeHitObject.EndTime == snappedTime) return; endTimeHitObject.EndTime = snappedTime;