From 6bc68ada4380a612c243e207c56d3691f60cce7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:15:53 +0900 Subject: [PATCH 1/7] Add ability to lock the `WaveformComparison` display to a current location --- .../Edit/Timing/WaveformComparisonDisplay.cs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index c80d3c4261..e86e75ad0d 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Timing { private const int total_waveforms = 8; + private const float corner_radius = LabelledDrawable.CORNER_RADIUS; + private readonly BindableNumber beatLength = new BindableDouble(); [Resolved] @@ -49,11 +52,18 @@ namespace osu.Game.Screens.Edit.Timing private readonly IBindableList controlPointGroups = new BindableList(); + private readonly BindableBool displayLocked = new BindableBool(); + + private LockedOverlay lockedOverlay = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + public WaveformComparisonDisplay() { RelativeSizeAxes = Axes.Both; - CornerRadius = LabelledDrawable.CORNER_RADIUS; + CornerRadius = corner_radius; Masking = true; } @@ -81,12 +91,19 @@ namespace osu.Game.Screens.Edit.Timing Width = 3, }); + AddInternal(lockedOverlay = new LockedOverlay()); + selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + + displayLocked.BindValueChanged(locked => + { + lockedOverlay.FadeTo(locked.NewValue ? 1 : 0, 200, Easing.OutQuint); + }, true); } private void updateTimingGroup() @@ -130,6 +147,12 @@ namespace osu.Game.Screens.Edit.Timing return base.OnMouseMove(e); } + protected override bool OnClick(ClickEvent e) + { + displayLocked.Toggle(); + return true; + } + protected override void Update() { base.Update(); @@ -147,6 +170,9 @@ namespace osu.Game.Screens.Edit.Timing if (lastDisplayedBeatIndex == beatIndex) return; + if (displayLocked.Value) + return; + // Chosen as a pretty usable number across all BPMs. // Optimally we'd want this to scale with the BPM in question, but performing // scaling of the display is both expensive in resampling, and decreases usability @@ -175,6 +201,64 @@ namespace osu.Game.Screens.Edit.Timing lastDisplayedBeatIndex = beatIndex; } + internal class LockedOverlay : CompositeDrawable + { + private OsuSpriteText text = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = corner_radius; + BorderColour = colours.Red; + BorderThickness = 3; + Alpha = 0; + + InternalChildren = new Drawable[] + { + new Box + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colours.Red, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuSpriteText + { + Colour = colours.GrayF, + Text = "Locked", + Margin = new MarginPadding(5), + Shadow = false, + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + text + .FadeIn().Then().Delay(500) + .FadeOut().Then().Delay(500) + .Loop(); + } + } + internal class WaveformRow : CompositeDrawable { private OsuSpriteText beatIndexText = null!; From c8f21ee8b2a8765477c62ff732678684869e010d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:32:02 +0900 Subject: [PATCH 2/7] Change `WaveformComparisonDisplay` to centre around a time offset rather than beat --- .../Edit/Timing/WaveformComparisonDisplay.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index e86e75ad0d..46ecf3d9f2 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Timing private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; - private int lastDisplayedBeatIndex; + private double displayedTime; private double selectedGroupStartTime; private double selectedGroupEndTime; @@ -56,9 +56,6 @@ namespace osu.Game.Screens.Edit.Timing private LockedOverlay lockedOverlay = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - public WaveformComparisonDisplay() { RelativeSizeAxes = Axes.Both; @@ -98,7 +95,7 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); - beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + beatLength.BindValueChanged(_ => regenerateDisplay(), true); displayLocked.BindValueChanged(locked => { @@ -139,10 +136,13 @@ namespace osu.Game.Screens.Edit.Timing protected override bool OnMouseMove(MouseMoveEvent e) { - float trackLength = (float)beatmap.Value.Track.Length; - int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); + if (!displayLocked.Value) + { + float trackLength = (float)beatmap.Value.Track.Length; + int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); - Scheduler.AddOnce(showFrom, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + Scheduler.AddOnce(showFromBeat, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + } return base.OnMouseMove(e); } @@ -157,21 +157,29 @@ namespace osu.Game.Screens.Edit.Timing { base.Update(); - if (!IsHovered) + if (!IsHovered && !displayLocked.Value) { int currentBeat = (int)Math.Floor((editorClock.CurrentTimeAccurate - selectedGroupStartTime) / timingPoint.BeatLength); - showFrom(currentBeat); + showFromBeat(currentBeat); } } - private void showFrom(int beatIndex) + private void showFromBeat(int beatIndex) => + showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength); + + private void showFromTime(double time) { - if (lastDisplayedBeatIndex == beatIndex) + if (displayedTime == time) return; - if (displayLocked.Value) - return; + displayedTime = time; + regenerateDisplay(); + } + + private void regenerateDisplay() + { + double index = (displayedTime - selectedGroupStartTime) / timingPoint.BeatLength; // Chosen as a pretty usable number across all BPMs. // Optimally we'd want this to scale with the BPM in question, but performing @@ -182,23 +190,25 @@ namespace osu.Game.Screens.Edit.Timing float trackLength = (float)beatmap.Value.Track.Length; float scale = trackLength / visible_width; + const int start_offset = total_waveforms / 2; + // Start displaying from before the current beat - beatIndex -= total_waveforms / 2; + index -= start_offset; foreach (var row in InternalChildren.OfType()) { // offset to the required beat index. - double time = selectedGroupStartTime + beatIndex * timingPoint.BeatLength; + double time = selectedGroupStartTime + index * timingPoint.BeatLength; float offset = (float)(time - visible_width / 2) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; row.WaveformOffset = -offset; row.WaveformScale = new Vector2(scale, 1); - row.BeatIndex = beatIndex++; - } + row.BeatIndex = (int)Math.Floor(index); - lastDisplayedBeatIndex = beatIndex; + index++; + } } internal class LockedOverlay : CompositeDrawable From 51014b8748a77964f0ff9b2266119e4e697e724b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:59:19 +0900 Subject: [PATCH 3/7] Ensure offset changes are correctly tracked by the display, even when locked --- .../Edit/Timing/WaveformComparisonDisplay.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 46ecf3d9f2..390965fbea 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -107,29 +107,37 @@ namespace osu.Game.Screens.Edit.Timing { beatLength.UnbindBindings(); - selectedGroupStartTime = 0; - selectedGroupEndTime = beatmap.Value.Track.Length; - var tcp = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault(); if (tcp == null) { timingPoint = new TimingControlPoint(); + // During movement of a control point's offset, this clause can be hit momentarily. + // We don't want to reset the `selectedGroupStartTime` here as we rely on having the + // last value to perform an offset traversal below. + selectedGroupEndTime = beatmap.Value.Track.Length; return; } timingPoint = tcp; beatLength.BindTo(timingPoint.BeatLengthBindable); - selectedGroupStartTime = selectedGroup.Value?.Time ?? 0; + double? newStartTime = selectedGroup.Value?.Time; + + if (newStartTime.HasValue && selectedGroupStartTime != newStartTime) + { + // The offset of the selected point may have changed. + // This handles the case the user has locked the view and expects the display to update with this change. + showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime)); + } var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints .SkipWhile(g => g != tcp) .Skip(1) .FirstOrDefault(); - if (nextGroup != null) - selectedGroupEndTime = nextGroup.Time; + selectedGroupStartTime = newStartTime ?? 0; + selectedGroupEndTime = nextGroup?.Time ?? beatmap.Value.Track.Length; } protected override bool OnHover(HoverEvent e) => true; From 94194a04f2931f5068b9ebb9e01de5f6dd13d8b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:05:38 +0900 Subject: [PATCH 4/7] Animate adjustments --- .../Edit/Timing/WaveformComparisonDisplay.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 390965fbea..172f0ac9b1 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); - beatLength.BindValueChanged(_ => regenerateDisplay(), true); + beatLength.BindValueChanged(_ => regenerateDisplay(true), true); displayLocked.BindValueChanged(locked => { @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing { // The offset of the selected point may have changed. // This handles the case the user has locked the view and expects the display to update with this change. - showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime)); + showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime), true); } var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints @@ -174,18 +174,18 @@ namespace osu.Game.Screens.Edit.Timing } private void showFromBeat(int beatIndex) => - showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength); + showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength, false); - private void showFromTime(double time) + private void showFromTime(double time, bool animated) { if (displayedTime == time) return; displayedTime = time; - regenerateDisplay(); + regenerateDisplay(animated); } - private void regenerateDisplay() + private void regenerateDisplay(bool animated) { double index = (displayedTime - selectedGroupStartTime) / timingPoint.BeatLength; @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Edit.Timing float offset = (float)(time - visible_width / 2) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; - row.WaveformOffset = -offset; + row.WaveformOffsetTo(-offset, animated); row.WaveformScale = new Vector2(scale, 1); row.BeatIndex = (int)Math.Floor(index); @@ -314,7 +314,15 @@ namespace osu.Game.Screens.Edit.Timing public int BeatIndex { set => beatIndexText.Text = value.ToString(); } public Vector2 WaveformScale { set => waveformGraph.Scale = value; } - public float WaveformOffset { set => waveformGraph.X = value; } + + public void WaveformOffsetTo(float value, bool animated) => + this.TransformTo(nameof(waveformOffset), value, animated ? 300 : 0, Easing.OutQuint); + + private float waveformOffset + { + get => waveformGraph.X; + set => waveformGraph.X = value; + } } } } From 475cc8174ff036f626f883ab6a75774c960a5e74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:17:08 +0900 Subject: [PATCH 5/7] Fix off-by-one display issue when adjusting offset --- .../Edit/Timing/WaveformComparisonDisplay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 172f0ac9b1..ce13a8a244 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -123,13 +123,7 @@ namespace osu.Game.Screens.Edit.Timing beatLength.BindTo(timingPoint.BeatLengthBindable); double? newStartTime = selectedGroup.Value?.Time; - - if (newStartTime.HasValue && selectedGroupStartTime != newStartTime) - { - // The offset of the selected point may have changed. - // This handles the case the user has locked the view and expects the display to update with this change. - showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime), true); - } + double? offsetChange = newStartTime - selectedGroupStartTime; var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints .SkipWhile(g => g != tcp) @@ -138,6 +132,13 @@ namespace osu.Game.Screens.Edit.Timing selectedGroupStartTime = newStartTime ?? 0; selectedGroupEndTime = nextGroup?.Time ?? beatmap.Value.Track.Length; + + if (newStartTime.HasValue && offsetChange.HasValue) + { + // The offset of the selected point may have changed. + // This handles the case the user has locked the view and expects the display to update with this change. + showFromTime(displayedTime + offsetChange.Value, true); + } } protected override bool OnHover(HoverEvent e) => true; From 58ba92772cd8acde594fb9dbc1ad6ba0271ecdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:01:34 +0900 Subject: [PATCH 6/7] Reword comment to read better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index ce13a8a244..1f12e2e5c9 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -112,9 +112,10 @@ namespace osu.Game.Screens.Edit.Timing if (tcp == null) { timingPoint = new TimingControlPoint(); - // During movement of a control point's offset, this clause can be hit momentarily. + // During movement of a control point's offset, this clause can be hit momentarily, + // as moving a control point is implemented by removing it and inserting it at the new time. // We don't want to reset the `selectedGroupStartTime` here as we rely on having the - // last value to perform an offset traversal below. + // last value to update the waveform display below. selectedGroupEndTime = beatmap.Value.Track.Length; return; } From f3fd5bbfc13b027cbae8d657e2d5b8d32ddf9f40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:05:29 +0900 Subject: [PATCH 7/7] Increase flash delay and ensure text is always shown immediately on lock --- .../Edit/Timing/WaveformComparisonDisplay.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 1f12e2e5c9..4275eaf7d3 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -99,7 +99,10 @@ namespace osu.Game.Screens.Edit.Timing displayLocked.BindValueChanged(locked => { - lockedOverlay.FadeTo(locked.NewValue ? 1 : 0, 200, Easing.OutQuint); + if (locked.NewValue) + lockedOverlay.Show(); + else + lockedOverlay.Hide(); }, true); } @@ -268,15 +271,20 @@ namespace osu.Game.Screens.Edit.Timing }; } - protected override void LoadComplete() + public override void Show() { - base.LoadComplete(); + this.FadeIn(100, Easing.OutQuint); text - .FadeIn().Then().Delay(500) - .FadeOut().Then().Delay(500) + .FadeIn().Then().Delay(600) + .FadeOut().Then().Delay(600) .Loop(); } + + public override void Hide() + { + this.FadeOut(100, Easing.OutQuint); + } } internal class WaveformRow : CompositeDrawable