From aee3e2a4ed0c66f5e5ec2257d2ac11e9cbbcb950 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 01:58:17 +0300 Subject: [PATCH 01/38] Add shadow effect to editor's bottom bar --- osu.Game/Screens/Edit/BottomBar.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 5994184c11..62caaced89 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -26,6 +29,14 @@ namespace osu.Game.Screens.Edit Height = 60; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.2f), + Type = EdgeEffectType.Shadow, + Radius = 10f, + }; + InternalChildren = new Drawable[] { new Box From 45841673f6704da52cd689836950c8967e0cae57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 15:44:16 +0900 Subject: [PATCH 02/38] Update `OnlineLookupCache` to use async version of `Perform` call --- osu.Game/Database/OnlineLookupCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index 2f98aef58a..506103a2c0 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -91,7 +91,7 @@ namespace osu.Game.Database } } - private void performLookup() + private async Task performLookup() { // contains at most 50 unique IDs from tasks, which is used to perform the lookup. var nextTaskBatch = new Dictionary>>(); @@ -127,7 +127,7 @@ namespace osu.Game.Database // rather than queueing, we maintain our own single-threaded request stream. // todo: we probably want retry logic here. - api.Perform(request); + await api.PerformAsync(request).ConfigureAwait(false); finishPendingTask(); From a2a057440e87e5f494a8248f07f851e3a505d460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:05:32 +0900 Subject: [PATCH 03/38] Fail requests taretting the fake API with a more deliberate exception I think this feels better than relying on some other method to throw an exception. --- osu.Game/Online/API/DummyAPIAccess.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index f292e95bd1..8614cc79cb 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -65,9 +65,7 @@ namespace osu.Game.Online.API { if (HandleRequest?.Invoke(request) != true) { - // this will fail due to not receiving an APIAccess, and trigger a failure on the request. - // this is intended - any request in testing that needs non-failures should use HandleRequest. - request.Perform(this); + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); } } From c18dd8c8fb29fb84c9459a3bed7bb58e429e257e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:32:44 +0900 Subject: [PATCH 04/38] Ensure `Queue` operations on `DummyAPIAccess` are performed on the update thread --- osu.Game/Online/API/DummyAPIAccess.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 8614cc79cb..d3707d977c 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -63,10 +63,13 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - if (HandleRequest?.Invoke(request) != true) + Schedule(() => { - request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); - } + if (HandleRequest?.Invoke(request) != true) + { + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); + } + }); } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); From f935f034c2a573bb5002b11d5f70b0987b2786a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:33:07 +0900 Subject: [PATCH 05/38] Ensure request handling for `OnlinePlayTestScene` runs in a scheduled fashion --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index b6a347a896..32e42a945d 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,7 +68,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. var beatmapManager = dependencies.Get(); - ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + ((DummyAPIAccess)API).HandleRequest = request => + { + TaskCompletionSource tcs = new TaskCompletionSource(); + + // Because some of the handlers use realm, we need to ensure the game is still alive when firing. + // If we don't, a stray `PerformAsync` could hit an `ObjectDisposedException` if running too late. + Scheduler.Add(() => + { + bool result = handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + tcs.SetResult(result); + }, false); + +#pragma warning disable RS0030 + // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to + // the task being a TaskCompletionSource. + return tcs.Task.Result; +#pragma warning restore RS0030 + }; }); /// From 0981d415a1fe5ed295b21049cc451f5817b36e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:57:48 +0900 Subject: [PATCH 06/38] Select correct channel regardless of load order --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 4edbb9f215..2cf1114f30 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -469,6 +469,8 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.Show(); }); + AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); + waitForChannel1Visible(); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); waitForChannel2Visible(); From 17174a7b099b753a2d6c8e7c90754f2060c8f163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 19:29:13 +0900 Subject: [PATCH 07/38] Ensure some results have been loaded in playlist results screen tests Basically, the failing delayed test would fire two web requests during the proceedings. In unfortunate timing, the first would succeed and the test would think "everything is okay", but the actual request loading results has not yet run. This check ensures *something* is loaded, which seems to be enough to make things reliable. --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index f5fe00458a..c532e8bc05 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Playlists { AddUntilStep("wait for scores loaded", () => requestComplete + // request handler may need to fire more than once to get scores. + && totalCount > 0 && resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount && resultsScreen.ScorePanelList.AllPanelsVisible); AddWaitStep("wait for display", 5); From 477e520766ef2924e4bbc37ab5cb33cf085debfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 12:24:44 +0900 Subject: [PATCH 08/38] Add comment regarding deadlock avoidance --- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 32e42a945d..df3974664e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -83,6 +83,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay #pragma warning disable RS0030 // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to // the task being a TaskCompletionSource. + // Importantly, this doesn't deadlock because of the scheduler call above running inline where feasible (see the `false` argument). return tcs.Task.Result; #pragma warning restore RS0030 }; From 27efeb7d4e1ce134aeb923d8f2f456d6b5b70f4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 14:58:49 +0900 Subject: [PATCH 09/38] Fix `TimingSection` performing a beatmap save when switching bound timing points --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index a5abd96d72..bebd4c1049 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -32,9 +32,11 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { + bpmTextEntry.Current.UnbindEvents(); bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + timeSignature.Current.UnbindEvents(); timeSignature.Current = point.NewValue.TimeSignatureBindable; timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } From cf5da44492a4d7da9c40d8e3f390e078e85cb94b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:00:30 +0900 Subject: [PATCH 10/38] Add automatic control point tracking to the timing screen --- .../Visual/Editing/TestSceneTimingScreen.cs | 48 ++++++++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 58 ++++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 17b8189fc7..3a709cb66d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -1,14 +1,18 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; +using osu.Game.Screens.Edit.Timing.RowAttributes; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -22,6 +26,8 @@ namespace osu.Game.Tests.Visual.Editing [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private TimingScreen timingScreen; + protected override bool ScrollUsingMouseWheel => false; public TestSceneTimingScreen() @@ -36,12 +42,52 @@ namespace osu.Game.Tests.Visual.Editing Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Disabled = true; - Child = new TimingScreen + Child = timingScreen = new TimingScreen { State = { Value = Visibility.Visible }, }; } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Stop clock", () => Clock.Stop()); + } + + [Test] + public void TestTrackingCurrentTimeWhileRunning() + { + AddStep("Select first effect point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); + AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + + AddStep("Seek to just before next point", () => Clock.Seek(69000)); + AddStep("Start clock", () => Clock.Start()); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); + } + + [Test] + public void TestTrackingCurrentTimeWhilePaused() + { + AddStep("Select first effect point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); + AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + + AddStep("Seek to later", () => Clock.Seek(80000)); + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f71a8d7d22..0cf4fca58a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,7 +20,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable selectedGroup = new Bindable(); + public readonly Bindable SelectedGroup = new Bindable(); public TimingScreen() : base(EditorScreenMode.Timing) @@ -132,6 +133,61 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + protected override void Update() + { + base.Update(); + + trackActivePoint(); + } + + /// + /// Given the user has selected a control point group, we want to track any group which is + /// active at the current point in time which matches the type the user has selected. + /// + /// So if the user is currently looking at a timing point and seeks into the future, a + /// future timing point would be automatically selected if it is now the new "current" point. + /// + private void trackActivePoint() + { + // For simplicity only match on the first type of the active control point. + var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + + if (selectedPointType != null) + { + // We don't have an efficient way of looking up groups currently, only individual point types. + // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. + IEnumerable groups = Beatmap.ControlPointInfo.Groups; + + bool currentTimeBeforeSelectedGroup = clock.CurrentTimeAccurate < selectedGroup.Value.Time; + + // Decide whether we are searching backwards or forwards. + if (currentTimeBeforeSelectedGroup) + groups = groups.Reverse(); + + // Find the next group which has the same type as the selected one. + groups = groups.SkipWhile(g => g != selectedGroup.Value) + .Skip(1) + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)); + + ControlPointGroup newGroup = groups.FirstOrDefault(); + + if (newGroup != null) + { + if (currentTimeBeforeSelectedGroup) + { + // When seeking backwards, the first match from the LINQ query is always what we want. + selectedGroup.Value = newGroup; + } + else + { + // When seeking forwards, we also need to check that the next match is before the current time. + if (newGroup.Time <= clock.CurrentTimeAccurate) + selectedGroup.Value = newGroup; + } + } + } + } + private void delete() { if (selectedGroup.Value == null) From ebb83a5e497e69027aaaa9cef28704f7688e6d22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:10:55 +0900 Subject: [PATCH 11/38] Add TODO about scroll behaviour which is missing --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 0c12eff503..77d875b67f 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -61,6 +61,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { + // TODO: This should scroll the selected row into view. foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue; }, true); } From 9746cbb1610383ccafc7957335a393b895c007f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:54:07 +0900 Subject: [PATCH 12/38] Ensure rows have loaded before attempting to click them --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 3a709cb66d..a358166477 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual.Editing public void SetUpSteps() { AddStep("Stop clock", () => Clock.Stop()); + + AddUntilStep("wait for rows to load", () => Child.ChildrenOfType().Any()); } [Test] From 655780fd986785d65db914275157825f0fad5046 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:27:18 +0900 Subject: [PATCH 13/38] Fix regression in bpm textbox binding logic --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index bebd4c1049..1a97058d73 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -28,17 +28,31 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + bpmTextEntry.Current.BindValueChanged(_ => saveChanges()); + timeSignature.Current.BindValueChanged(_ => saveChanges()); + + void saveChanges() + { + if (!isRebinding) ChangeHandler?.SaveState(); + } + } + + private bool isRebinding; + protected override void OnControlPointChanged(ValueChangedEvent point) { if (point.NewValue != null) { - bpmTextEntry.Current.UnbindEvents(); - bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; - bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + isRebinding = true; - timeSignature.Current.UnbindEvents(); + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; timeSignature.Current = point.NewValue.TimeSignatureBindable; - timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + + isRebinding = false; } } From 6bc68ada4380a612c243e207c56d3691f60cce7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:15:53 +0900 Subject: [PATCH 14/38] 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 15/38] 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 16/38] 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 17/38] 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 18/38] 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 a940676fc2ae5d24b34a8c500a8d6bc9fc4a57a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:10:02 +0900 Subject: [PATCH 19/38] Add adjustment buttons --- .../Editing/TestSceneTapTimingControl.cs | 6 + osu.Game/Screens/Edit/Timing/AdjustButton.cs | 235 ++++++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 65 ++++- 3 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Timing/AdjustButton.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 46b45979ea..8dd368f2a9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -77,6 +77,12 @@ namespace osu.Game.Tests.Visual.Editing timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}"; } + [Test] + public void TestNoop() + { + AddStep("do nothing", () => { }); + } + [Test] public void TestTapThenReset() { diff --git a/osu.Game/Screens/Edit/Timing/AdjustButton.cs b/osu.Game/Screens/Edit/Timing/AdjustButton.cs new file mode 100644 index 0000000000..b0a0517e94 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/AdjustButton.cs @@ -0,0 +1,235 @@ +// 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.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Timing +{ + public class AdjustButton : CompositeDrawable + { + public Action Action; + + private readonly double adjustAmount; + private ScheduledDelegate adjustDelegate; + + private const int adjust_levels = 4; + + private const double initial_delay = 300; + + private const double minimum_delay = 80; + + public Container Content { get; set; } + + private double adjustDelay = initial_delay; + + private readonly Box background; + + private readonly OsuSpriteText text; + + public LocalisableString Text + { + get => text?.Text ?? default; + set + { + if (text != null) + text.Text = value; + } + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public AdjustButton(double adjustAmount) + { + this.adjustAmount = adjustAmount; + + CornerRadius = 5; + Masking = true; + + AddInternal(Content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold), + Padding = new MarginPadding(5), + Depth = float.MinValue + } + } + }); + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Background3; + + for (int i = 1; i <= adjust_levels; i++) + { + Content.Add(new IncrementBox(i, adjustAmount)); + Content.Add(new IncrementBox(-i, adjustAmount)); + } + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + beginRepeat(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + adjustDelegate?.Cancel(); + base.OnMouseUp(e); + } + + private void beginRepeat() + { + adjustDelegate?.Cancel(); + + adjustDelay = initial_delay; + adjustNext(); + + void adjustNext() + { + var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); + + if (hoveredBox != null) + { + Action(adjustAmount * hoveredBox.Multiplier); + + adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); + + hoveredBox.Flash(); + } + else + { + adjustDelay = initial_delay; + } + + adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); + } + } + + private class IncrementBox : CompositeDrawable + { + public readonly float Multiplier; + + private readonly Box box; + private readonly OsuSpriteText text; + + public IncrementBox(int index, double amount) + { + Multiplier = Math.Sign(index) * convertMultiplier(index); + + float ratio = (float)index / adjust_levels; + + RelativeSizeAxes = Axes.Both; + + Width = 0.5f * Math.Abs(ratio); + + Anchor direction = index < 0 ? Anchor.x2 : Anchor.x0; + + Origin |= direction; + + Depth = Math.Abs(index); + + Anchor = Anchor.TopCentre; + + InternalChildren = new Drawable[] + { + box = new Box + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive + }, + text = new OsuSpriteText + { + Anchor = direction, + Origin = direction, + Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), + Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}", + Padding = new MarginPadding(5), + Alpha = 0, + } + }; + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + box.Colour = colourProvider.Background1; + box.Alpha = 0.1f; + } + + private float convertMultiplier(int m) + { + switch (Math.Abs(m)) + { + default: return 1; + + case 2: return 2; + + case 3: return 5; + + case 4: return 10; + } + } + + protected override bool OnHover(HoverEvent e) + { + box.Colour = colourProvider.Colour0; + + box.FadeTo(0.2f, 100, Easing.OutQuint); + text.FadeIn(100, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + box.Colour = colourProvider.Background1; + + box.FadeTo(0.1f, 500, Easing.OutQuint); + text.FadeOut(100, Easing.OutQuint); + base.OnHoverLost(e); + } + + public void Flash() + { + box + .FadeTo(0.4f, 20, Easing.OutQuint) + .Then() + .FadeTo(0.2f, 400, Easing.OutQuint); + + text + .MoveToY(-5, 20, Easing.OutQuint) + .Then() + .MoveToY(0, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index d0ab4d1f98..f9f5a20405 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,6 +19,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap beatmap { get; set; } + [Resolved] private Bindable selectedGroup { get; set; } @@ -45,6 +49,7 @@ namespace osu.Game.Screens.Edit.Timing { new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 60), }, Content = new[] { @@ -77,7 +82,36 @@ namespace osu.Game.Screens.Edit.Timing }, } } - } + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new AdjustButton(1) + { + Text = "Offset", + RelativeSizeAxes = Axes.X, + Width = 0.48f, + Height = 50, + Action = adjustOffset, + }, + new AdjustButton(0.1) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "BPM", + RelativeSizeAxes = Axes.X, + Width = 0.48f, + Height = 50, + Action = adjustBpm, + } + } + }, }, new Drawable[] { @@ -113,6 +147,35 @@ namespace osu.Game.Screens.Edit.Timing }; } + private void adjustOffset(double adjust) + { + // VERY TEMPORARY + var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); + + beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + double newOffset = selectedGroup.Value.Time + adjust; + + foreach (var cp in currentGroupItems) + beatmap.ControlPointInfo.Add(newOffset, cp); + + // the control point might not necessarily exist yet, if currentGroupItems was empty. + selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); + + if (!editorClock.IsRunning) + editorClock.Seek(newOffset); + } + + private void adjustBpm(double adjust) + { + var timing = selectedGroup.Value.ControlPoints.OfType().FirstOrDefault(); + + if (timing == null) + return; + + timing.BeatLength = 60000 / (timing.BPM + adjust); + } + private void tap() { editorClock.Seek(selectedGroup.Value.Time); From f99bcb23a0d296ef7cfd462276d86e730ed13cd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:49:32 +0900 Subject: [PATCH 20/38] Automatically make first control point added to beatmap have timing data --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f71a8d7d22..c3b804c2d6 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -144,7 +144,14 @@ namespace osu.Game.Screens.Edit.Timing private void addNew() { - selectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); + bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any(); + + var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); + + if (isFirstControlPoint) + group.Add(new TimingControlPoint()); + + selectedGroup.Value = group; } } } From 8240b645b4c93c2d83d2deaa31c49274e8920463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:49:47 +0900 Subject: [PATCH 21/38] Copy attribute types from currently selected control point to new placements --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index c3b804c2d6..5ee6106c46 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -150,6 +151,23 @@ namespace osu.Game.Screens.Edit.Timing if (isFirstControlPoint) group.Add(new TimingControlPoint()); + else + { + // Try and create matching types from the currently selected control point. + var selected = selectedGroup.Value; + + if (selected != null) + { + foreach (var controlPoint in selected.ControlPoints) + { + if (Activator.CreateInstance(controlPoint.GetType()) is ControlPoint copy) + { + copy.CopyFrom(controlPoint); + group.Add(copy); + } + } + } + } selectedGroup.Value = group; } From bc22079fdcf4c06d89c38298ba83a526abc6f7b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 22:02:30 +0900 Subject: [PATCH 22/38] Fix row selected colour flicker when changing offset rapidly --- osu.Game/Screens/Edit/EditorTable.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index a67a060134..26819dcfe7 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -99,6 +99,15 @@ namespace osu.Game.Screens.Edit colourSelected = colours.Colour3; } + protected override void LoadComplete() + { + base.LoadComplete(); + + // Reduce flicker of rows when offset is being changed rapidly. + // Probably need to reconsider this. + FinishTransforms(true); + } + private bool selected; public bool Selected From 25941f618786ffd007f2be0e7be303d424593b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 10:47:45 +0900 Subject: [PATCH 23/38] Use `DeepClone` instead of reflection call 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/TimingScreen.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5ee6106c46..b94f3e3ef4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -160,11 +160,7 @@ namespace osu.Game.Screens.Edit.Timing { foreach (var controlPoint in selected.ControlPoints) { - if (Activator.CreateInstance(controlPoint.GetType()) is ControlPoint copy) - { - copy.CopyFrom(controlPoint); - group.Add(copy); - } + group.Add(controlPoint.DeepClone()); } } } From 6042cf1a3b9fe2255346853ce791f037823d7ef6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 14:36:25 +0900 Subject: [PATCH 24/38] Add metronome sound Placeholder pending follow-up from @nekodex (which as discussed should probably have a second sound for the metronome locking into "stopped" position). --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 57fcff6a4c..1f065d1551 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -31,12 +33,16 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; + private Sample clunk; + [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + clunk = audio.Samples.Get(@"Multiplayer/countdown-tick"); + const float taper = 25; const float swing_vertical_offset = -23; const float lower_cover_height = 32; @@ -269,8 +275,15 @@ namespace osu.Game.Screens.Edit.Timing if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { - using (stick.BeginDelayedSequence(beatLength / 2)) + using (BeginDelayedSequence(beatLength / 2)) + { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + Schedule(() => + { + clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + clunk?.Play(); + }); + } } } } From 58ba92772cd8acde594fb9dbc1ad6ba0271ecdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:01:34 +0900 Subject: [PATCH 25/38] 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 26/38] 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 From c55c3325a4c7df43dbd8df314e29c9dc7e21354c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:43:40 +0900 Subject: [PATCH 27/38] Remove unused using statements --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b94f3e3ef4..3c59cfff64 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,7 +1,6 @@ // 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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From 8c54bd46bb2209e8f09965b2a2e5e06d82311e2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:46:05 +0900 Subject: [PATCH 28/38] Rename button to more appropriate name --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 4 ++-- .../Edit/Timing/{AdjustButton.cs => TimingAdjustButton.cs} | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) rename osu.Game/Screens/Edit/Timing/{AdjustButton.cs => TimingAdjustButton.cs} (96%) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index f9f5a20405..990f8d2ce0 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Timing Padding = new MarginPadding(10), Children = new Drawable[] { - new AdjustButton(1) + new TimingAdjustButton(1) { Text = "Offset", RelativeSizeAxes = Axes.X, @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Timing Height = 50, Action = adjustOffset, }, - new AdjustButton(0.1) + new TimingAdjustButton(0.1) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Screens/Edit/Timing/AdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs similarity index 96% rename from osu.Game/Screens/Edit/Timing/AdjustButton.cs rename to osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index b0a0517e94..335f593772 100644 --- a/osu.Game/Screens/Edit/Timing/AdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -16,7 +16,10 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Timing { - public class AdjustButton : CompositeDrawable + /// + /// A button with variable constant output based on hold position and length. + /// + public class TimingAdjustButton : CompositeDrawable { public Action Action; @@ -50,7 +53,7 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider colourProvider { get; set; } - public AdjustButton(double adjustAmount) + public TimingAdjustButton(double adjustAmount) { this.adjustAmount = adjustAmount; From 1293bbdbd931a7f17f5c5249e69593d0f16430c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:46:33 +0900 Subject: [PATCH 29/38] Remove unnecessary null checks on `Text` property --- osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 335f593772..46bb8356f7 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -42,12 +42,8 @@ namespace osu.Game.Screens.Edit.Timing public LocalisableString Text { - get => text?.Text ?? default; - set - { - if (text != null) - text.Text = value; - } + get => text.Text; + set => text.Text = value; } [Resolved] From 187acb0718bda64c693f19ea9da3bb041d556161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:57:53 +0900 Subject: [PATCH 30/38] Simplify linq tracking logic --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 30 ++------------------ 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0cf4fca58a..ebb958f884 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -156,35 +156,11 @@ namespace osu.Game.Screens.Edit.Timing { // We don't have an efficient way of looking up groups currently, only individual point types. // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. - IEnumerable groups = Beatmap.ControlPointInfo.Groups; - - bool currentTimeBeforeSelectedGroup = clock.CurrentTimeAccurate < selectedGroup.Value.Time; - - // Decide whether we are searching backwards or forwards. - if (currentTimeBeforeSelectedGroup) - groups = groups.Reverse(); // Find the next group which has the same type as the selected one. - groups = groups.SkipWhile(g => g != selectedGroup.Value) - .Skip(1) - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)); - - ControlPointGroup newGroup = groups.FirstOrDefault(); - - if (newGroup != null) - { - if (currentTimeBeforeSelectedGroup) - { - // When seeking backwards, the first match from the LINQ query is always what we want. - selectedGroup.Value = newGroup; - } - else - { - // When seeking forwards, we also need to check that the next match is before the current time. - if (newGroup.Time <= clock.CurrentTimeAccurate) - selectedGroup.Value = newGroup; - } - } + selectedGroup.Value = Beatmap.ControlPointInfo.Groups + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); } } From 0472881078490377697b66a7b4201dc341d87d84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:03:03 +0900 Subject: [PATCH 31/38] Fix null check only covering one of two calls --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 1f065d1551..3a067a281e 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -278,11 +278,15 @@ namespace osu.Game.Screens.Edit.Timing using (BeginDelayedSequence(beatLength / 2)) { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - Schedule(() => + + if (clunk != null) { - clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - clunk?.Play(); - }); + Schedule(() => + { + clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + clunk.Play(); + }); + } } } } From 93b8c90ecca5cae08e7fce1b331f3c0f0bfe5739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:04:13 +0900 Subject: [PATCH 32/38] Apply frequency adjust to channel rather than sample for safety --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 3a067a281e..4143c5ea55 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -279,14 +279,16 @@ namespace osu.Game.Screens.Edit.Timing { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - if (clunk != null) + Schedule(() => { - Schedule(() => + var channel = clunk?.GetChannel(); + + if (channel != null) { - clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - clunk.Play(); - }); - } + channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + channel.Play(); + } + }); } } } From a4ec32b499dabcc1280033b1fe37688fbec1f5c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:02:17 +0900 Subject: [PATCH 33/38] Add button sound effect --- .../Screens/Edit/Timing/TimingAdjustButton.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 46bb8356f7..9fc7e56a3d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -4,6 +4,8 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly double adjustAmount; private ScheduledDelegate adjustDelegate; + private const int max_multiplier = 10; + private const int adjust_levels = 4; private const double initial_delay = 300; @@ -40,6 +44,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly OsuSpriteText text; + private Sample sample; + public LocalisableString Text { get => text.Text; @@ -79,8 +85,10 @@ namespace osu.Game.Screens.Edit.Timing } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + sample = audio.Samples.Get(@"UI/notch-tick"); + background.Colour = colourProvider.Background3; for (int i = 1; i <= adjust_levels; i++) @@ -120,6 +128,17 @@ namespace osu.Game.Screens.Edit.Timing adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); hoveredBox.Flash(); + + var channel = sample?.GetChannel(); + + if (channel != null) + { + double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); + double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f; + + channel.Frequency.Value = 1 + multiplierModifier + repeatModifier; + channel.Play(); + } } else { @@ -195,7 +214,8 @@ namespace osu.Game.Screens.Edit.Timing case 3: return 5; - case 4: return 10; + case 4: + return max_multiplier; } } From 534e8f8fac69c2df110922f17b1b6f79ecfde98f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:20:16 +0900 Subject: [PATCH 34/38] Only switch to found group if non-null --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index ebb958f884..d7ca306037 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -158,9 +158,12 @@ namespace osu.Game.Screens.Edit.Timing // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. // Find the next group which has the same type as the selected one. - selectedGroup.Value = Beatmap.ControlPointInfo.Groups - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) - .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); + var found = Beatmap.ControlPointInfo.Groups + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); + + if (found != null) + selectedGroup.Value = found; } } From cb37dd74c19f9bb21c754128f8850123f1a83359 Mon Sep 17 00:00:00 2001 From: "Hugo \"ThePooN\" Denizart" Date: Wed, 1 Jun 2022 17:14:07 +0200 Subject: [PATCH 35/38] =?UTF-8?q?=F0=9F=94=A7=20Add=20prefix=20to=20Sentry?= =?UTF-8?q?=20release=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sentry-release.yml | 2 +- osu.Game/Utils/SentryLogger.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 8ca9f38234..794943c4b1 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -23,4 +23,4 @@ jobs: SENTRY_URL: https://sentry.ppy.sh/ with: environment: production - version: ${{ github.ref }} + version: osu/${{ github.ref_name }} diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 137bf7e0aa..a4f8ea0ef6 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -48,9 +48,8 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; - // The reported release needs to match release tags on github in order for sentry - // to automatically associate and track against releases. - options.Release = game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty); + // The reported release needs to match version as reported to Sentry in .github/workflows/sentry-release.yml + options.Release = $"osu/{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; }); Logger.NewEntry += processLogEntry; From 384cdcbc40e0a193a7e8d9102e44b93579eef869 Mon Sep 17 00:00:00 2001 From: "Hugo \"ThePooN\" Denizart" Date: Wed, 1 Jun 2022 18:12:29 +0200 Subject: [PATCH 36/38] =?UTF-8?q?=F0=9F=9A=91=20Fix=20use=20of=20illegal?= =?UTF-8?q?=20character=20in=20Sentry's=20release=20name=20scheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sentry-release.yml | 2 +- osu.Game/Utils/SentryLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 794943c4b1..442b97c473 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -23,4 +23,4 @@ jobs: SENTRY_URL: https://sentry.ppy.sh/ with: environment: production - version: osu/${{ github.ref_name }} + version: osu@${{ github.ref_name }} diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index a4f8ea0ef6..ecc7fc4774 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -49,7 +49,7 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; // The reported release needs to match version as reported to Sentry in .github/workflows/sentry-release.yml - options.Release = $"osu/{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; + options.Release = $"osu@{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; }); Logger.NewEntry += processLogEntry; From 6b709de2c26d259676e158e35a228e82eab13071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 1 Jun 2022 20:18:02 +0200 Subject: [PATCH 37/38] Remove unused using directive --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d7ca306037..d104cc4b5c 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From 801e11d8418b49018d9174629d413e14bd7b36ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 1 Jun 2022 20:25:56 +0200 Subject: [PATCH 38/38] Apply performance regression fix to effect section too --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index c8944d0357..c9f73411f1 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -31,18 +31,33 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + kiai.Current.BindValueChanged(_ => saveChanges()); + omitBarLine.Current.BindValueChanged(_ => saveChanges()); + scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges()); + + void saveChanges() + { + if (!isRebinding) ChangeHandler?.SaveState(); + } + } + + private bool isRebinding; + protected override void OnControlPointChanged(ValueChangedEvent point) { if (point.NewValue != null) { + isRebinding = true; + kiai.Current = point.NewValue.KiaiModeBindable; - kiai.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; - omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - scrollSpeedSlider.Current = point.NewValue.ScrollSpeedBindable; - scrollSpeedSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + + isRebinding = false; } }