From a8cbd400d36b8997a0ba87ae1ec55227381764fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:17:13 +0900 Subject: [PATCH 01/63] Ensure virtual track time is long enough for test beatmaps --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 5 ++--- osu.Game/Tests/Visual/OsuTestScene.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index ab4fb38657..1e43e5d148 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -180,9 +180,8 @@ namespace osu.Game.Tests.Beatmaps private readonly BeatmapInfo skinBeatmapInfo; private readonly IResourceStore resourceStore; - public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, - double length = 60000) - : base(beatmap, storyboard, referenceClock, audio, length) + public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) + : base(beatmap, storyboard, referenceClock, audio) { this.skinBeatmapInfo = skinBeatmapInfo; this.resourceStore = resourceStore; diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index b59a1db403..6e2fd0a6d7 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -23,6 +23,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens; using osu.Game.Storyboards; @@ -222,18 +223,19 @@ namespace osu.Game.Tests.Visual /// The storyboard. /// An optional clock which should be used instead of a stopwatch for virtual time progression. /// Audio manager. Required if a reference clock isn't provided. - /// The length of the returned virtual track. - public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { + double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(length); + track = store.GetVirtual(lastObjectTime); } else - track = audio?.Tracks.GetVirtual(length); + track = audio?.Tracks.GetVirtual(lastObjectTime); } ~ClockBackedTestWorkingBeatmap() From 21bf93a7c2b6d07e4e825c6b14f59a4ea3edd0af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:29:36 +0900 Subject: [PATCH 02/63] Ensure there's a buffer after the last hitobject to allow certain replay tests to complete correctly --- osu.Game/Tests/Visual/OsuTestScene.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 6e2fd0a6d7..e3f07dbad4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,16 +226,18 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - double lastObjectTime = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 60000; + var lastHitObject = beatmap.HitObjects.LastOrDefault(); + + double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; if (referenceClock != null) { store = new TrackVirtualStore(referenceClock); audio.AddItem(store); - track = store.GetVirtual(lastObjectTime); + track = store.GetVirtual(trackLength); } else - track = audio?.Tracks.GetVirtual(lastObjectTime); + track = audio?.Tracks.GetVirtual(trackLength); } ~ClockBackedTestWorkingBeatmap() From 4b8188065504cf88de4c8cb487a180f1a0696904 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:04:04 +0900 Subject: [PATCH 03/63] Account for potentially longer non-last objects --- osu.Game/Tests/Visual/OsuTestScene.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index e3f07dbad4..8886188d95 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -226,9 +226,11 @@ namespace osu.Game.Tests.Visual public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio) : base(beatmap, storyboard, audio) { - var lastHitObject = beatmap.HitObjects.LastOrDefault(); + double trackLength = 60000; - double trackLength = lastHitObject?.GetEndTime() + 2000 ?? 60000; + if (beatmap.HitObjects.Count > 0) + // add buffer after last hitobject to allow for final replay frames etc. + trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; if (referenceClock != null) { From 7fead6ee41dcf4a242eec2d0b13df47d6c2fd50c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 14:22:32 +0900 Subject: [PATCH 04/63] Add comment making mania test behaviour clearer --- osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs index ab840e1c46..e8c2472c3b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests objects.Add(new Note { StartTime = time }); + // don't hit the first note if (i > 0) { frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1)); From ead3c195674a73f054dabccf0cfdb0d7f193bd58 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 13:40:42 -0500 Subject: [PATCH 05/63] added function so circle is deleted when shift+right click --- .../Compose/Components/SelectionHandler.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 4caceedc5a..54e62649e1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { + private bool shiftPressed; + public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -164,6 +167,17 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; + protected override bool OnKeyDown(KeyDownEvent e) + { + shiftPressed = e.ShiftPressed; + return false; + } + + protected override void OnKeyUp(KeyUpEvent e) + { + shiftPressed = e.ShiftPressed; + } + public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -455,6 +469,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { + if (shiftPressed) + { + deleteSelected(); + return null; + } + if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From 123967056693f0f4b34d0eb04dc103ec39a33548 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:28:53 -0500 Subject: [PATCH 06/63] moved right click shift delete functionality to HandleSelectionRequested + reduced func size --- .../Compose/Components/SelectionHandler.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 54e62649e1..eeeacce4a7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -33,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - private bool shiftPressed; public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -167,17 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether any s could be reversed. public virtual bool HandleReverse() => false; - protected override bool OnKeyDown(KeyDownEvent e) - { - shiftPressed = e.ShiftPressed; - return false; - } - - protected override void OnKeyUp(KeyUpEvent e) - { - shiftPressed = e.ShiftPressed; - } - public bool OnPressed(PlatformAction action) { switch (action.ActionMethod) @@ -237,6 +226,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + { + shiftClickDeleteCheck(blueprint, state); + multiSelectionHandler(blueprint, state); + + } + + private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -255,6 +251,15 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + { + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + { + EditorBeatmap.Remove(blueprint.HitObject); + return; + } + } + private void deleteSelected() { EditorBeatmap.RemoveRange(selectedBlueprints.Select(b => b.HitObject)); @@ -469,12 +474,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { get { - if (shiftPressed) - { - deleteSelected(); - return null; - } - if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); From ccaf6560ec619004ddd67b57dd62674f7b6520db Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:30:37 -0500 Subject: [PATCH 07/63] formatting --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index eeeacce4a7..f4b98c66b1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public class SelectionHandler : CompositeDrawable, IKeyBindingHandler, IHasContextMenu { - public IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; @@ -229,7 +228,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { shiftClickDeleteCheck(blueprint, state); multiSelectionHandler(blueprint, state); - } private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) From 255bb9d10092ede439c0d8c5f71b7ca707880a37 Mon Sep 17 00:00:00 2001 From: Charlie Date: Mon, 26 Oct 2020 14:52:59 -0500 Subject: [PATCH 08/63] fixed issue with returns --- .../Edit/Compose/Components/SelectionHandler.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f4b98c66b1..f0a9e69321 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -226,11 +226,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - shiftClickDeleteCheck(blueprint, state); - multiSelectionHandler(blueprint, state); + if (!shiftClickDeleteCheck(blueprint, state)) + handleMultiSelection(blueprint, state); } - private void multiSelectionHandler(SelectionBlueprint blueprint, InputState state) + private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -249,13 +249,14 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - private void shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) + private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) { EditorBeatmap.Remove(blueprint.HitObject); - return; + return true; } + return false; } private void deleteSelected() From 3f8c4c57d0682441a69bfed1c5a69bda07ef0979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Oct 2020 22:16:28 +0100 Subject: [PATCH 09/63] Fix code style issues & restructure --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 5 +++ .../Compose/Components/SelectionHandler.cs | 43 ++++++------------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 4abdbfc244..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -120,6 +120,11 @@ namespace osu.Game.Rulesets.Edit /// public void Deselect() => State = SelectionState.NotSelected; + /// + /// Toggles the selection state of this . + /// + public void ToggleSelection() => State = IsSelected ? SelectionState.NotSelected : SelectionState.Selected; + public bool IsSelected => State == SelectionState.Selected; /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index f0a9e69321..9cddb69d0b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Audio; using osu.Game.Graphics; @@ -225,38 +224,22 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) - { - if (!shiftClickDeleteCheck(blueprint, state)) - handleMultiSelection(blueprint, state); - } - - private void handleMultiSelection(SelectionBlueprint blueprint, InputState state) - { - if (state.Keyboard.ControlPressed) - { - if (blueprint.IsSelected) - blueprint.Deselect(); - else - blueprint.Select(); - } - else - { - if (blueprint.IsSelected) - return; - - DeselectAll?.Invoke(); - blueprint.Select(); - } - } - - private bool shiftClickDeleteCheck(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - { EditorBeatmap.Remove(blueprint.HitObject); - return true; - } - return false; + else if (state.Keyboard.ControlPressed) + blueprint.ToggleSelection(); + else + ensureSelected(blueprint); + } + + private void ensureSelected(SelectionBlueprint blueprint) + { + if (blueprint.IsSelected) + return; + + DeselectAll?.Invoke(); + blueprint.Select(); } private void deleteSelected() From 27c1a4c4d3693dbc1f22b2c9427646c8ccf01977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 12:53:54 +0900 Subject: [PATCH 10/63] Move right-click deletion logic to be handled at a SelectionBlueprint level --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 +++++++++++++++++ .../Edit/Compose/Components/SelectionHandler.cs | 4 +--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index d3fb5defae..ca9ec886d5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - return false; // Allow right click to be handled by context menu + break; case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return false; + return base.OnMouseDown(e); } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index f3816f6218..87ef7e647f 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,10 +8,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -52,6 +55,20 @@ namespace osu.Game.Rulesets.Edit updateState(); } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) + { + editorBeatmap.Remove(HitObject); + return true; + } + + return base.OnMouseDown(e); + } + private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9cddb69d0b..036edbeb84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -225,9 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) - EditorBeatmap.Remove(blueprint.HitObject); - else if (state.Keyboard.ControlPressed) + if (state.Keyboard.ControlPressed) blueprint.ToggleSelection(); else ensureSelected(blueprint); From 3c2e2f29bc8eb190daab56fea86ab00a1925871b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Oct 2020 13:17:44 +0900 Subject: [PATCH 11/63] Remove unused using statement --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 036edbeb84..24f88bf36d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { From 742a96484befff6d8c137cd749cfd648c7c65992 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 20:13:18 +0300 Subject: [PATCH 12/63] Add ability to set extra parameters to SearchBeatmapSetsRequest --- .../API/Requests/SearchBeatmapSetsRequest.cs | 28 ++++++++++++++++++- .../Overlays/BeatmapListing/SearchExtra.cs | 13 +++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchExtra.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index dde45b5aeb..f8cf747757 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -21,6 +21,8 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + public SearchExtra Extra { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -35,7 +37,8 @@ namespace osu.Game.Online.API.Requests SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, - SearchLanguage language = SearchLanguage.Any) + SearchLanguage language = SearchLanguage.Any, + SearchExtra extra = SearchExtra.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -46,6 +49,7 @@ namespace osu.Game.Online.API.Requests SortDirection = sortDirection; Genre = genre; Language = language; + Extra = extra; } protected override WebRequest CreateWebRequest() @@ -68,6 +72,28 @@ namespace osu.Game.Online.API.Requests req.AddCursor(cursor); + if (Extra != SearchExtra.Any) + { + string extraString = string.Empty; + + switch (Extra) + { + case SearchExtra.Both: + extraString = "video.storyboard"; + break; + + case SearchExtra.Storyboard: + extraString = "storyboard"; + break; + + case SearchExtra.Video: + extraString = "video"; + break; + } + + req.AddParameter("e", extraString); + } + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs new file mode 100644 index 0000000000..fd4896c46e --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchExtra + { + Video, + Storyboard, + Both, + Any + } +} From 26a60d898cfd3404275ad59021931bf4a03ec94a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:22:20 +0300 Subject: [PATCH 13/63] Implement BeatmapSearchExtraFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 + .../BeatmapListingFilterControl.cs | 4 +- .../BeatmapListingSearchControl.cs | 4 + .../BeatmapSearchExtraFilterRow.cs | 97 +++++++++++++ .../BeatmapListing/BeatmapSearchFilterRow.cs | 127 +++++++++--------- .../BeatmapSearchRulesetFilterRow.cs | 3 +- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 7 files changed, 176 insertions(+), 66 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a4698a9a32..9a410dd18c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText category; OsuSpriteText genre; OsuSpriteText language; + OsuSpriteText extra; Add(control = new BeatmapListingSearchControl { @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), + extra = new OsuSpriteText() } }); @@ -54,6 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); } [Test] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 494a0df8f8..37fbfe7093 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,6 +130,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -179,7 +180,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.Current.Value, sortControl.SortDirection.Value, searchControl.Genre.Value, - searchControl.Language.Value); + searchControl.Language.Value, + searchControl.Extra.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 29c4fe0d2e..437c26e36d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; + public Bindable Extra => extraFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -48,6 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; + private readonly BeatmapSearchExtraFilterRow extraFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -105,6 +108,7 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), + extraFilter = new BeatmapSearchExtraFilterRow() } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs new file mode 100644 index 0000000000..6e81cd2976 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + { + public BeatmapSearchExtraFilterRow() + : base("Extra") + { + } + + protected override Drawable CreateFilter() => new ExtraFilter(); + + private class ExtraFilter : FillFlowContainer, IHasCurrentValue + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly ExtraFilterTabItem videoItem; + private readonly ExtraFilterTabItem storyboardItem; + + public ExtraFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(new[] + { + videoItem = new ExtraFilterTabItem(SearchExtra.Video), + storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) + }); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + private void updateBindable() + { + if (videoItem.Active.Value && storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Both; + return; + } + + if (videoItem.Active.Value) + { + Current.Value = SearchExtra.Video; + return; + } + + if (storyboardItem.Active.Value) + { + Current.Value = SearchExtra.Storyboard; + return; + } + + Current.Value = SearchExtra.Any; + } + } + + private class ExtraFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public ExtraFilterTabItem(SearchExtra value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + + protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 45ef793deb..ad32475b25 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing public BeatmapSearchFilterRow(string headerName) { + Drawable filter; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; AddInternal(new GridContainer @@ -49,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing }, Content = new[] { - new Drawable[] + new[] { new OsuSpriteText { @@ -58,17 +59,17 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13), Text = headerName.Titleize() }, - CreateFilter().With(f => - { - f.Current = current; - }) + filter = CreateFilter() } } }); + + if (filter is IHasCurrentValue filterWithValue) + filterWithValue.Current = current; } [NotNull] - protected virtual BeatmapSearchFilter CreateFilter() => new BeatmapSearchFilter(); + protected virtual Drawable CreateFilter() => new BeatmapSearchFilter(); protected class BeatmapSearchFilter : TabControl { @@ -99,62 +100,6 @@ namespace osu.Game.Overlays.BeatmapListing protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } - private class FilterDropdown : OsuTabDropdown { protected override DropdownHeader CreateHeader() => new FilterHeader @@ -172,5 +117,63 @@ namespace osu.Game.Overlays.BeatmapListing } } } + + protected class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs index eebd896cf9..a8dc088e52 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRulesetFilterRow.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Rulesets; namespace osu.Game.Overlays.BeatmapListing @@ -13,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override BeatmapSearchFilter CreateFilter() => new RulesetFilter(); + protected override Drawable CreateFilter() => new RulesetFilter(); private class RulesetFilter : BeatmapSearchFilter { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index fd4896c46e..53900211e1 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,9 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + Any, Video, Storyboard, - Both, - Any + Both } } From 1b40b56d41081be1a199bea0889727360e57de1c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 21:30:53 +0300 Subject: [PATCH 14/63] Add ability to search by play criteria --- .../TestSceneBeatmapListingSearchControl.cs | 5 ++++- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 9 ++++++++- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 +++- .../BeatmapListing/BeatmapListingSearchControl.cs | 6 +++++- osu.Game/Overlays/BeatmapListing/SearchPlayed.cs | 12 ++++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/SearchPlayed.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9a410dd18c..5c9431aad1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText played; Add(control = new BeatmapListingSearchControl { @@ -47,7 +48,8 @@ namespace osu.Game.Tests.Visual.UserInterface category = new OsuSpriteText(), genre = new OsuSpriteText(), language = new OsuSpriteText(), - extra = new OsuSpriteText() + extra = new OsuSpriteText(), + played = new OsuSpriteText() } }); @@ -57,6 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } [Test] diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f8cf747757..12383e7457 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,6 +23,8 @@ namespace osu.Game.Online.API.Requests public SearchExtra Extra { get; } + public SearchPlayed Played { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -38,7 +40,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any) + SearchExtra extra = SearchExtra.Any, + SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -50,6 +53,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Played = played; } protected override WebRequest CreateWebRequest() @@ -94,6 +98,9 @@ namespace osu.Game.Online.API.Requests req.AddParameter("e", extraString); } + if (Played != SearchPlayed.Any) + req.AddParameter("played", Played.ToString().ToLowerInvariant()); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 37fbfe7093..3f09a7e3d1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); sortDirection.BindValueChanged(_ => queueUpdateSearch()); @@ -181,7 +182,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value); + searchControl.Extra.Value, + searchControl.Played.Value); getSetsRequest.Success += response => { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 437c26e36d..80beed6217 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Extra => extraFilter.Current; + public Bindable Played => playedFilter.Current; + public BeatmapSetInfo BeatmapSet { set @@ -51,6 +53,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; private readonly UpdateableBeatmapSetCover beatmapCover; @@ -108,7 +111,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow() + extraFilter = new BeatmapSearchExtraFilterRow(), + playedFilter = new BeatmapSearchFilterRow(@"Played") } } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs new file mode 100644 index 0000000000..eb7fb46158 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchPlayed.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchPlayed + { + Any, + Played, + Unplayed + } +} From 1710b396e7195f34b024166d74129701ffc21eac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 22:27:29 +0300 Subject: [PATCH 15/63] Implement BeatmapSearchMultipleSelectionFilterRow --- .../TestSceneBeatmapListingSearchControl.cs | 3 +- .../API/Requests/SearchBeatmapSetsRequest.cs | 33 ++------ .../BeatmapListingSearchControl.cs | 3 +- .../BeatmapSearchExtraFilterRow.cs | 79 ++---------------- .../BeatmapListing/BeatmapSearchFilterRow.cs | 64 +------------- ...BeatmapSearchMultipleSelectionFilterRow.cs | 83 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 74 +++++++++++++++++ .../Overlays/BeatmapListing/SearchExtra.cs | 4 +- 8 files changed, 179 insertions(+), 164 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/FilterTabItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 5c9431aad1..9b0ef4d6f2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {e.NewValue}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 12383e7457..20bbd46529 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.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.Collections.Generic; +using System.Linq; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -21,7 +23,7 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public SearchExtra Extra { get; } + public List Extra { get; } public SearchPlayed Played { get; } @@ -40,7 +42,7 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - SearchExtra extra = SearchExtra.Any, + List extra = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -74,33 +76,14 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); - req.AddCursor(cursor); - - if (Extra != SearchExtra.Any) - { - string extraString = string.Empty; - - switch (Extra) - { - case SearchExtra.Both: - extraString = "video.storyboard"; - break; - - case SearchExtra.Storyboard: - extraString = "storyboard"; - break; - - case SearchExtra.Video: - extraString = "video"; - break; - } - - req.AddParameter("e", extraString); - } + if (Extra != null && Extra.Any()) + req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); + req.AddCursor(cursor); + return req; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 80beed6217..f390db3f35 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable Extra => extraFilter.Current; + public Bindable> Extra => extraFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 6e81cd2976..385978096c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -1,94 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osuTK; - namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapSearchExtraFilterRow : BeatmapSearchFilterRow + public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow { public BeatmapSearchExtraFilterRow() : base("Extra") { } - protected override Drawable CreateFilter() => new ExtraFilter(); + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - private class ExtraFilter : FillFlowContainer, IHasCurrentValue + private class ExtraFilter : MultipleSelectionFilter { - private readonly BindableWithCurrent current = new BindableWithCurrent(); - - public Bindable Current + protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] { - get => current.Current; - set => current.Current = value; - } - - private readonly ExtraFilterTabItem videoItem; - private readonly ExtraFilterTabItem storyboardItem; - - public ExtraFilter() - { - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - RelativeSizeAxes = Axes.X; - Height = 15; - Spacing = new Vector2(10, 0); - AddRange(new[] - { - videoItem = new ExtraFilterTabItem(SearchExtra.Video), - storyboardItem = new ExtraFilterTabItem(SearchExtra.Storyboard) - }); - - foreach (var item in Children) - item.StateUpdated += updateBindable; - } - - private void updateBindable() - { - if (videoItem.Active.Value && storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Both; - return; - } - - if (videoItem.Active.Value) - { - Current.Value = SearchExtra.Video; - return; - } - - if (storyboardItem.Active.Value) - { - Current.Value = SearchExtra.Storyboard; - return; - } - - Current.Value = SearchExtra.Any; - } + new ExtraFilterTabItem(SearchExtra.Video), + new ExtraFilterTabItem(SearchExtra.Storyboard) + }; } - private class ExtraFilterTabItem : FilterTabItem + private class ExtraFilterTabItem : MultipleSelectionFilterTabItem { - public event Action StateUpdated; - public ExtraFilterTabItem(SearchExtra value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); - } - - protected override bool OnClick(ClickEvent e) - { - base.OnClick(e); - Active.Value = !Active.Value; - return true; } protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index ad32475b25..aa0fc0d00e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -1,20 +1,16 @@ // 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; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using Humanizer; using osu.Game.Utils; @@ -98,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapListing protected override Dropdown CreateDropdown() => new FilterDropdown(); - protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); + protected override TabItem CreateTabItem(T value) => new FilterTabItem(value); private class FilterDropdown : OsuTabDropdown { @@ -117,63 +113,5 @@ namespace osu.Game.Overlays.BeatmapListing } } } - - protected class FilterTabItem : TabItem - { - protected virtual float TextSize => 13; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - - private readonly OsuSpriteText text; - - public FilterTabItem(T value) - : base(value) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - AddRangeInternal(new Drawable[] - { - text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), - Text = CreateText(value) - }, - new HoverClickSounds() - }); - - Enabled.Value = true; - } - - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - - [BackgroundDependencyLoader] - private void load() - { - updateState(); - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); - - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; - } } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs new file mode 100644 index 0000000000..c434e00ff3 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -0,0 +1,83 @@ +// 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 osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Overlays.BeatmapListing +{ + public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + { + public BeatmapSearchMultipleSelectionFilterRow(string headerName) + : base(headerName) + { + } + + protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + + protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); + + protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + { + private readonly BindableWithCurrent> current = new BindableWithCurrent>(); + + public Bindable> Current + { + get => current.Current; + set => current.Current = value; + } + + public MultipleSelectionFilter() + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + RelativeSizeAxes = Axes.X; + Height = 15; + Spacing = new Vector2(10, 0); + AddRange(CreateItems()); + + foreach (var item in Children) + item.StateUpdated += updateBindable; + } + + protected abstract MultipleSelectionFilterTabItem[] CreateItems(); + + private void updateBindable() + { + var selectedValues = new List(); + + foreach (var item in Children) + { + if (item.Active.Value) + selectedValues.Add(item.Value); + } + + Current.Value = selectedValues; + } + } + + protected class MultipleSelectionFilterTabItem : FilterTabItem + { + public event Action StateUpdated; + + public MultipleSelectionFilterTabItem(T value) + : base(value) + { + Active.BindValueChanged(_ => StateUpdated?.Invoke()); + } + + protected override bool OnClick(ClickEvent e) + { + base.OnClick(e); + Active.Value = !Active.Value; + return true; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs new file mode 100644 index 0000000000..32b48862e0 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class FilterTabItem : TabItem + { + protected virtual float TextSize => 13; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public FilterTabItem(T value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + AddRangeInternal(new Drawable[] + { + text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Text = CreateText(value) + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + + private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + } +} diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 53900211e1..e9b3165d97 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -5,9 +5,7 @@ namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { - Any, Video, - Storyboard, - Both + Storyboard } } From 008d1d697cee99eee4e5cf076dcb209f71189a9f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:14:48 +0300 Subject: [PATCH 16/63] Implement filtering by rank achieved --- .../TestSceneBeatmapListingSearchControl.cs | 3 ++ .../API/Requests/SearchBeatmapSetsRequest.cs | 7 ++++ .../BeatmapListingFilterControl.cs | 2 ++ .../BeatmapListingSearchControl.cs | 4 +++ .../BeatmapSearchExtraFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 4 +-- .../BeatmapSearchRankFilterRow.cs | 35 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 10 +++--- .../Overlays/BeatmapListing/SearchRank.cs | 24 +++++++++++++ 9 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs create mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 9b0ef4d6f2..ff8162293b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface OsuSpriteText genre; OsuSpriteText language; OsuSpriteText extra; + OsuSpriteText ranks; OsuSpriteText played; Add(control = new BeatmapListingSearchControl @@ -50,6 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface genre = new OsuSpriteText(), language = new OsuSpriteText(), extra = new OsuSpriteText(), + ranks = new OsuSpriteText(), played = new OsuSpriteText() } }); @@ -60,6 +62,7 @@ namespace osu.Game.Tests.Visual.UserInterface control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 20bbd46529..f819a5778f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,6 +27,8 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } + public List Ranks { get; } + private readonly string query; private readonly RulesetInfo ruleset; private readonly Cursor cursor; @@ -43,6 +45,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, List extra = null, + List ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); @@ -55,6 +58,7 @@ namespace osu.Game.Online.API.Requests Genre = genre; Language = language; Extra = extra; + Ranks = ranks; Played = played; } @@ -79,6 +83,9 @@ namespace osu.Game.Online.API.Requests if (Extra != null && Extra.Any()) req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + if (Ranks != null && Ranks.Any()) + req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 3f09a7e3d1..86bf3276fe 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -131,6 +131,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,6 +184,7 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Genre.Value, searchControl.Language.Value, searchControl.Extra.Value, + searchControl.Ranks.Value, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index f390db3f35..64bd7065b5 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable> Extra => extraFilter.Current; + public Bindable> Ranks => ranksFilter.Current; + public Bindable Played => playedFilter.Current; public BeatmapSetInfo BeatmapSet @@ -54,6 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchExtraFilterRow extraFilter; + private readonly BeatmapSearchRankFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -113,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchExtraFilterRow(), + ranksFilter = new BeatmapSearchRankFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs index 385978096c..d8bf18fb88 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing private class ExtraFilter : MultipleSelectionFilter { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new[] + protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] { new ExtraFilterTabItem(SearchExtra.Video), new ExtraFilterTabItem(SearchExtra.Storyboard) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index c434e00ff3..acf2c62afa 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.BeatmapListing { public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - public BeatmapSearchMultipleSelectionFilterRow(string headerName) + protected BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - public MultipleSelectionFilter() + protected MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs new file mode 100644 index 0000000000..d521e8c90f --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs @@ -0,0 +1,35 @@ +// 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.Extensions; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchRankFilterRow() + : base("Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem[] CreateItems() + => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); + } + + private class RankFilterTabItem : MultipleSelectionFilterTabItem + { + public RankFilterTabItem(SearchRank value) + : base(value) + { + } + + protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 32b48862e0..9bdd5b3fad 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -16,8 +16,6 @@ namespace osu.Game.Overlays.BeatmapListing { public class FilterTabItem : TabItem { - protected virtual float TextSize => 13; - [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -33,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { text = new OsuSpriteText { - Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Regular), + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = CreateText(value) }, new HoverClickSounds() @@ -67,7 +65,11 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - private void updateState() => text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + private void updateState() + { + text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); + } private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; } diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs new file mode 100644 index 0000000000..8b1882026c --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/SearchRank.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Overlays.BeatmapListing +{ + public enum SearchRank + { + [Description(@"Silver SS")] + XH, + + [Description(@"SS")] + X, + + [Description(@"Silver S")] + SH, + S, + A, + B, + C, + D + } +} From c4efceceb2a384af3468db13839c6668675038c3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Oct 2020 23:57:11 +0300 Subject: [PATCH 17/63] Use char instead of sting for request parameter creation --- .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 4 ++-- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index ff8162293b..e07aa71b1f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join(".", e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join(".", r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); + control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index f819a5778f..248096d8b3 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -81,10 +81,10 @@ namespace osu.Game.Online.API.Requests req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}"); if (Extra != null && Extra.Any()) - req.AddParameter("e", string.Join(".", Extra.Select(e => e.ToString().ToLowerInvariant()))); + req.AddParameter("e", string.Join('.', Extra.Select(e => e.ToString().ToLowerInvariant()))); if (Ranks != null && Ranks.Any()) - req.AddParameter("r", string.Join(".", Ranks.Select(r => r.ToString()))); + req.AddParameter("r", string.Join('.', Ranks.Select(r => r.ToString()))); if (Played != SearchPlayed.Any) req.AddParameter("played", Played.ToString().ToLowerInvariant()); From b4ec3b9fefa6206558cf3f3e0ea6541cac0948e6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:41:46 +0300 Subject: [PATCH 18/63] Simplify MultipleSelectionFilterTabItem state changes --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index acf2c62afa..35c982d35a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.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.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing AddRange(CreateItems()); foreach (var item in Children) - item.StateUpdated += updateBindable; + item.Active.BindValueChanged(_ => updateBindable()); } protected abstract MultipleSelectionFilterTabItem[] CreateItems(); @@ -64,18 +63,15 @@ namespace osu.Game.Overlays.BeatmapListing protected class MultipleSelectionFilterTabItem : FilterTabItem { - public event Action StateUpdated; - public MultipleSelectionFilterTabItem(T value) : base(value) { - Active.BindValueChanged(_ => StateUpdated?.Invoke()); } protected override bool OnClick(ClickEvent e) { base.OnClick(e); - Active.Value = !Active.Value; + Active.Toggle(); return true; } } From fd11346a289e03b0a5f2f1f2cd5d1da3a578fffe Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 01:48:24 +0300 Subject: [PATCH 19/63] Update button colours --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 9bdd5b3fad..721a3c839c 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -67,10 +67,10 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { - text.FadeColour(Active.Value ? Color4.White : getStateColour(), 200, Easing.OutQuint); + text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - private Color4 getStateColour() => IsHovered ? colourProvider.Light1 : colourProvider.Light3; + private Color4 getStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; } } From 03c5057a921d8ddf21666671c76e23bc396b8a60 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:28:31 +0300 Subject: [PATCH 20/63] Simplify BeatmapSearchMultipleSelectionFilterRow --- .../BeatmapListingSearchControl.cs | 8 ++--- .../BeatmapSearchExtraFilterRow.cs | 34 ------------------ ...BeatmapSearchMultipleSelectionFilterRow.cs | 21 ++++++----- .../BeatmapSearchRankFilterRow.cs | 35 ------------------- .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +-- .../Overlays/BeatmapListing/SearchExtra.cs | 4 +++ 6 files changed, 19 insertions(+), 87 deletions(-) delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 64bd7065b5..a976890c7c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -55,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; - private readonly BeatmapSearchExtraFilterRow extraFilter; - private readonly BeatmapSearchRankFilterRow ranksFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; + private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,8 +115,8 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter = new BeatmapSearchFilterRow(@"Categories"), genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), - extraFilter = new BeatmapSearchExtraFilterRow(), - ranksFilter = new BeatmapSearchRankFilterRow(), + extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), + ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs deleted file mode 100644 index d8bf18fb88..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchExtraFilterRow.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchExtraFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchExtraFilterRow() - : base("Extra") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new ExtraFilter(); - - private class ExtraFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() => new MultipleSelectionFilterTabItem[] - { - new ExtraFilterTabItem(SearchExtra.Video), - new ExtraFilterTabItem(SearchExtra.Storyboard) - }; - } - - private class ExtraFilterTabItem : MultipleSelectionFilterTabItem - { - public ExtraFilterTabItem(SearchExtra value) - : base(value) - { - } - - protected override string CreateText(SearchExtra value) => $@"Has {value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 35c982d35a..cb89560e39 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -1,8 +1,10 @@ // 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 osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -11,18 +13,16 @@ using osuTK; namespace osu.Game.Overlays.BeatmapListing { - public abstract class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { - protected BeatmapSearchMultipleSelectionFilterRow(string headerName) + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { } - protected override Drawable CreateFilter() => CreateMultipleSelectionFilter(); + protected override Drawable CreateFilter() => new MultipleSelectionFilter(); - protected abstract MultipleSelectionFilter CreateMultipleSelectionFilter(); - - protected abstract class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> { private readonly BindableWithCurrent> current = new BindableWithCurrent>(); @@ -32,21 +32,20 @@ namespace osu.Game.Overlays.BeatmapListing set => current.Current = value; } - protected MultipleSelectionFilter() + public MultipleSelectionFilter() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; RelativeSizeAxes = Axes.X; Height = 15; Spacing = new Vector2(10, 0); - AddRange(CreateItems()); + + ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(_ => updateBindable()); } - protected abstract MultipleSelectionFilterTabItem[] CreateItems(); - private void updateBindable() { var selectedValues = new List(); @@ -61,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - protected class MultipleSelectionFilterTabItem : FilterTabItem + private class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs deleted file mode 100644 index d521e8c90f..0000000000 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchRankFilterRow.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.Extensions; - -namespace osu.Game.Overlays.BeatmapListing -{ - public class BeatmapSearchRankFilterRow : BeatmapSearchMultipleSelectionFilterRow - { - public BeatmapSearchRankFilterRow() - : base("Rank Achieved") - { - } - - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); - - private class RankFilter : MultipleSelectionFilter - { - protected override MultipleSelectionFilterTabItem[] CreateItems() - => ((SearchRank[])Enum.GetValues(typeof(SearchRank))).Select(v => new RankFilterTabItem(v)).ToArray(); - } - - private class RankFilterTabItem : MultipleSelectionFilterTabItem - { - public RankFilterTabItem(SearchRank value) - : base(value) - { - } - - protected override string CreateText(SearchRank value) => $@"{value.GetDescription() ?? value.ToString()}"; - } - } -} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 721a3c839c..244ef5a703 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = (value as Enum)?.GetDescription() ?? value.ToString() }, new HoverClickSounds() }); @@ -40,8 +40,6 @@ namespace osu.Game.Overlays.BeatmapListing Enabled.Value = true; } - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index e9b3165d97..0ee60c4a95 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Overlays.BeatmapListing { public enum SearchExtra { + [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } } From 6fd3686c4d443ca4c4daad14e1f0885d7747b26f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:36:35 +0300 Subject: [PATCH 21/63] Use IReadOnlyCollection instead of List in SearchBeatmapSetsRequest --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 248096d8b3..708b58d954 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -23,11 +23,11 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } - public List Extra { get; } + public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } - public List Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -44,8 +44,8 @@ namespace osu.Game.Online.API.Requests SortDirection sortDirection = SortDirection.Descending, SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, - List extra = null, - List ranks = null, + IReadOnlyCollection extra = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); From 914bd537885c946c8bd5fda14aecde6c8b3bc90d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 02:39:51 +0300 Subject: [PATCH 22/63] Add missing blank line --- osu.Game/Overlays/BeatmapListing/SearchExtra.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs index 0ee60c4a95..af37e3264f 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchExtra.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchExtra.cs @@ -9,6 +9,7 @@ namespace osu.Game.Overlays.BeatmapListing { [Description("Has Video")] Video, + [Description("Has Storyboard")] Storyboard } From 01b576c8611bfca9f3d2ba3bad5974ed06688759 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 13:32:39 +0900 Subject: [PATCH 23/63] Fix editor crash on exit when forcing exit twice in a row --- osu.Game/Screens/Edit/Editor.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c3560dff38..0aaa551af9 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -444,12 +444,21 @@ namespace osu.Game.Screens.Edit if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) { confirmExit(); - return true; + return false; } if (isNewBeatmap || HasUnsavedChanges) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + dialogOverlay?.Push(new PromptForSaveDialog(() => + { + confirmExit(); + this.Exit(); + }, () => + { + confirmExitWithSave(); + this.Exit(); + })); + return true; } } @@ -464,7 +473,6 @@ namespace osu.Game.Screens.Edit { exitConfirmed = true; Save(); - this.Exit(); } private void confirmExit() @@ -483,7 +491,6 @@ namespace osu.Game.Screens.Edit } exitConfirmed = true; - this.Exit(); } private readonly Bindable clipboard = new Bindable(); From 4f6081c7f3f48fcedc899665a32e89b4a757e45c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 19:44:13 +0300 Subject: [PATCH 24/63] Use BindableList --- .../TestSceneBeatmapListingSearchControl.cs | 4 +-- .../BeatmapListingFilterControl.cs | 8 ++--- .../BeatmapListingSearchControl.cs | 5 ++- .../BeatmapListing/BeatmapSearchFilterRow.cs | 2 +- ...BeatmapSearchMultipleSelectionFilterRow.cs | 35 ++++++++----------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index e07aa71b1f..3f757031f8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.UserInterface control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true); - control.Extra.BindValueChanged(e => extra.Text = $"Extra: {(e.NewValue == null ? "" : string.Join('.', e.NewValue.Select(i => i.ToString().ToLowerInvariant())))}", true); - control.Ranks.BindValueChanged(r => ranks.Text = $"Ranks: {(r.NewValue == null ? "" : string.Join('.', r.NewValue.Select(i => i.ToString())))}", true); + control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true); + control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true); control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true); } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 86bf3276fe..71f0d8c522 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Ranks.BindValueChanged(_ => queueUpdateSearch()); + searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); @@ -183,8 +183,8 @@ namespace osu.Game.Overlays.BeatmapListing sortControl.SortDirection.Value, searchControl.Genre.Value, searchControl.Language.Value, - searchControl.Extra.Value, - searchControl.Ranks.Value, + searchControl.Extra, + searchControl.Ranks, searchControl.Played.Value); getSetsRequest.Success += response => diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index a976890c7c..4fc5c5315b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; -using System.Collections.Generic; namespace osu.Game.Overlays.BeatmapListing { @@ -29,9 +28,9 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable Language => languageFilter.Current; - public Bindable> Extra => extraFilter.Current; + public BindableList Extra => extraFilter.Current; - public Bindable> Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index aa0fc0d00e..b429a5277b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.BeatmapListing }); if (filter is IHasCurrentValue filterWithValue) - filterWithValue.Current = current; + Current = filterWithValue.Current; } [NotNull] diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index cb89560e39..993b475f32 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osuTK; @@ -15,22 +14,21 @@ namespace osu.Game.Overlays.BeatmapListing { public class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> { + public new readonly BindableList Current = new BindableList(); + + private MultipleSelectionFilter filter; + public BeatmapSearchMultipleSelectionFilterRow(string headerName) : base(headerName) { + Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer, IHasCurrentValue> + private class MultipleSelectionFilter : FillFlowContainer { - private readonly BindableWithCurrent> current = new BindableWithCurrent>(); - - public Bindable> Current - { - get => current.Current; - set => current.Current = value; - } + public readonly BindableList Current = new BindableList(); public MultipleSelectionFilter() { @@ -43,20 +41,15 @@ namespace osu.Game.Overlays.BeatmapListing ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); foreach (var item in Children) - item.Active.BindValueChanged(_ => updateBindable()); + item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } - private void updateBindable() + private void updateBindable(T value, bool active) { - var selectedValues = new List(); - - foreach (var item in Children) - { - if (item.Active.Value) - selectedValues.Add(item.Value); - } - - Current.Value = selectedValues; + if (active) + Current.Add(value); + else + Current.Remove(value); } } From 5c2c5f200075a4f7f19a089a9db3775a11b73668 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 28 Oct 2020 23:35:08 +0300 Subject: [PATCH 25/63] Use existing ScoreRank for rank filter --- .../API/Requests/SearchBeatmapSetsRequest.cs | 5 +- .../BeatmapListingSearchControl.cs | 7 +-- ...BeatmapSearchMultipleSelectionFilterRow.cs | 14 ++++-- .../BeatmapSearchScoreFilterRow.cs | 48 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 4 +- .../Overlays/BeatmapListing/SearchRank.cs | 24 ---------- 6 files changed, 68 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs delete mode 100644 osu.Game/Overlays/BeatmapListing/SearchRank.cs diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 708b58d954..ed67c5f5ca 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -8,6 +8,7 @@ using osu.Game.Extensions; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests { @@ -27,7 +28,7 @@ namespace osu.Game.Online.API.Requests public SearchPlayed Played { get; } - public IReadOnlyCollection Ranks { get; } + public IReadOnlyCollection Ranks { get; } private readonly string query; private readonly RulesetInfo ruleset; @@ -45,7 +46,7 @@ namespace osu.Game.Online.API.Requests SearchGenre genre = SearchGenre.Any, SearchLanguage language = SearchLanguage.Any, IReadOnlyCollection extra = null, - IReadOnlyCollection ranks = null, + IReadOnlyCollection ranks = null, SearchPlayed played = SearchPlayed.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 4fc5c5315b..3694c9855e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing { @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing public BindableList Extra => extraFilter.Current; - public BindableList Ranks => ranksFilter.Current; + public BindableList Ranks => ranksFilter.Current; public Bindable Played => playedFilter.Current; @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly BeatmapSearchFilterRow genreFilter; private readonly BeatmapSearchFilterRow languageFilter; private readonly BeatmapSearchMultipleSelectionFilterRow extraFilter; - private readonly BeatmapSearchMultipleSelectionFilterRow ranksFilter; + private readonly BeatmapSearchScoreFilterRow ranksFilter; private readonly BeatmapSearchFilterRow playedFilter; private readonly Box background; @@ -115,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapListing genreFilter = new BeatmapSearchFilterRow(@"Genre"), languageFilter = new BeatmapSearchFilterRow(@"Language"), extraFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Extra"), - ranksFilter = new BeatmapSearchMultipleSelectionFilterRow(@"Rank Achieved"), + ranksFilter = new BeatmapSearchScoreFilterRow(), playedFilter = new BeatmapSearchFilterRow(@"Played") } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 993b475f32..87e60c5bdd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -24,9 +24,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = new MultipleSelectionFilter(); + protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); - private class MultipleSelectionFilter : FillFlowContainer + protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); + + protected class MultipleSelectionFilter : FillFlowContainer { public readonly BindableList Current = new BindableList(); @@ -38,12 +40,16 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - ((T[])Enum.GetValues(typeof(T))).ForEach(i => Add(new MultipleSelectionFilterTabItem(i))); + GetValues().ForEach(i => Add(CreateTabItem(i))); foreach (var item in Children) item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); } + protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + + protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); + private void updateBindable(T value, bool active) { if (active) @@ -53,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - private class MultipleSelectionFilterTabItem : FilterTabItem + protected class MultipleSelectionFilterTabItem : FilterTabItem { public MultipleSelectionFilterTabItem(T value) : base(value) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs new file mode 100644 index 0000000000..f741850f07 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -0,0 +1,48 @@ +// 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.Game.Scoring; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapSearchScoreFilterRow : BeatmapSearchMultipleSelectionFilterRow + { + public BeatmapSearchScoreFilterRow() + : base(@"Rank Achieved") + { + } + + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new RankFilter(); + + private class RankFilter : MultipleSelectionFilter + { + protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); + + protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + } + + private class RankItem : MultipleSelectionFilterTabItem + { + public RankItem(ScoreRank value) + : base(value) + { + } + + protected override string CreateText(ScoreRank value) + { + switch (value) + { + case ScoreRank.XH: + return @"Silver SS"; + + case ScoreRank.SH: + return @"Silver S"; + + default: + return base.CreateText(value); + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 244ef5a703..c45a82bef1 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = (value as Enum)?.GetDescription() ?? value.ToString() + Text = CreateText(value) }, new HoverClickSounds() }); @@ -63,6 +63,8 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); + protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + private void updateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : getStateColour(), 200, Easing.OutQuint); diff --git a/osu.Game/Overlays/BeatmapListing/SearchRank.cs b/osu.Game/Overlays/BeatmapListing/SearchRank.cs deleted file mode 100644 index 8b1882026c..0000000000 --- a/osu.Game/Overlays/BeatmapListing/SearchRank.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.ComponentModel; - -namespace osu.Game.Overlays.BeatmapListing -{ - public enum SearchRank - { - [Description(@"Silver SS")] - XH, - - [Description(@"SS")] - X, - - [Description(@"Silver S")] - SH, - S, - A, - B, - C, - D - } -} From 202fe093065ef46560555d78402fbf3c49f8a762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:03:59 +0100 Subject: [PATCH 26/63] Group selection actions back up in SelectionHandler --- .../Sliders/SliderSelectionBlueprint.cs | 4 ++-- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 17 ----------------- .../Compose/Components/BlueprintContainer.cs | 7 ------- .../Edit/Compose/Components/SelectionHandler.cs | 5 ++++- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index ca9ec886d5..d3fb5defae 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -107,14 +107,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { case MouseButton.Right: rightClickPosition = e.MouseDownPosition; - break; + return false; // Allow right click to be handled by context menu case MouseButton.Left when e.ControlPressed && IsSelected: placementControlPointIndex = addControlPoint(e.MousePosition); return true; // Stop input from being handled and modifying the selection } - return base.OnMouseDown(e); + return false; } private int? placementControlPointIndex; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 87ef7e647f..f3816f6218 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -8,13 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Screens.Edit; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Edit { @@ -55,20 +52,6 @@ namespace osu.Game.Rulesets.Edit updateState(); } - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.CurrentState.Keyboard.ShiftPressed && e.IsPressed(MouseButton.Right)) - { - editorBeatmap.Remove(HitObject); - return true; - } - - return base.OnMouseDown(e); - } - private SelectionState state; public event Action StateChanged; diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7751df29cf..5ac360d029 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -298,13 +298,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - // Deselections are only allowed for control + left clicks - bool allowDeselection = e.ControlPressed && e.Button == MouseButton.Left; - - // Todo: This is probably incorrectly disallowing multiple selections on stacked objects - if (!allowDeselection && SelectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) - return; - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) { if (blueprint.IsHovered) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 24f88bf36d..01e23bafc5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { @@ -224,7 +225,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The input state at the point of selection. internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { - if (state.Keyboard.ControlPressed) + if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) + EditorBeatmap.Remove(blueprint.HitObject); + else if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) blueprint.ToggleSelection(); else ensureSelected(blueprint); From fa53549ed271a7ff3486ff77835c93baa03ec48d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:57:03 +0100 Subject: [PATCH 27/63] Mark request fields as possibly-null --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ed67c5f5ca..bbaa7e745f 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Overlays; @@ -24,10 +25,12 @@ namespace osu.Game.Online.API.Requests public SearchLanguage Language { get; } + [CanBeNull] public IReadOnlyCollection Extra { get; } public SearchPlayed Played { get; } + [CanBeNull] public IReadOnlyCollection Ranks { get; } private readonly string query; From e77049eae39afb8cf363197079ccd9a4257c07b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 22:58:51 +0100 Subject: [PATCH 28/63] Use discard-like lambda parameter names --- .../Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 71f0d8c522..3be38e3c1d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.BeatmapListing searchControl.Category.BindValueChanged(_ => queueUpdateSearch()); searchControl.Genre.BindValueChanged(_ => queueUpdateSearch()); searchControl.Language.BindValueChanged(_ => queueUpdateSearch()); - searchControl.Extra.CollectionChanged += (u, v) => queueUpdateSearch(); - searchControl.Ranks.CollectionChanged += (u, v) => queueUpdateSearch(); + searchControl.Extra.CollectionChanged += (_, __) => queueUpdateSearch(); + searchControl.Ranks.CollectionChanged += (_, __) => queueUpdateSearch(); searchControl.Played.BindValueChanged(_ => queueUpdateSearch()); sortCriteria.BindValueChanged(_ => queueUpdateSearch()); From f5aedc96c4244974750c651a94976f17ed283ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:07:54 +0100 Subject: [PATCH 29/63] Rework multiple selection filter --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 23 ++++++++++++------- .../BeatmapSearchScoreFilterRow.cs | 3 ++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 87e60c5bdd..ae669e91dd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -32,7 +33,8 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly BindableList Current = new BindableList(); - public MultipleSelectionFilter() + [BackgroundDependencyLoader] + private void load() { Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; @@ -40,17 +42,22 @@ namespace osu.Game.Overlays.BeatmapListing Height = 15; Spacing = new Vector2(10, 0); - GetValues().ForEach(i => Add(CreateTabItem(i))); - - foreach (var item in Children) - item.Active.BindValueChanged(active => updateBindable(item.Value, active.NewValue)); + AddRange(GetValues().Select(CreateTabItem)); } - protected virtual T[] GetValues() => (T[])Enum.GetValues(typeof(T)); + protected override void LoadComplete() + { + base.LoadComplete(); + + foreach (var item in Children) + item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); + } + + protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); - private void updateBindable(T value, bool active) + private void toggleItem(T value, bool active) { if (active) Current.Add(value); diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index f741850f07..dc642cb4cd 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.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.Game.Scoring; @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapListing { protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); - protected override ScoreRank[] GetValues() => base.GetValues().Reverse().ToArray(); + protected override IEnumerable GetValues() => base.GetValues().Reverse(); } private class RankItem : MultipleSelectionFilterTabItem From a8cefb0d4ce014b5944559188d2faf063fdfcf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:12:28 +0100 Subject: [PATCH 30/63] Rename method --- .../Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs | 5 +++-- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index dc642cb4cd..804962adfb 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions; using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapListing @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing { } - protected override string CreateText(ScoreRank value) + protected override string LabelFor(ScoreRank value) { switch (value) { @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapListing return @"Silver S"; default: - return base.CreateText(value); + return value.GetDescription(); } } } diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index c45a82bef1..43913c7ce2 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = CreateText(value) + Text = LabelFor(value) }, new HoverClickSounds() }); @@ -63,7 +63,10 @@ namespace osu.Game.Overlays.BeatmapListing protected override void OnDeactivated() => updateState(); - protected virtual string CreateText(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); + /// + /// Returns the label text to be used for the supplied . + /// + protected virtual string LabelFor(T value) => (value as Enum)?.GetDescription() ?? value.ToString(); private void updateState() { From 016e920aa9fff8a7b4c2aaf4236a70cfff5b3038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:14:52 +0100 Subject: [PATCH 31/63] Move filter tab item hierarchy construction to BDL --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 43913c7ce2..f02b515755 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -19,10 +19,15 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly OsuSpriteText text; + private OsuSpriteText text; public FilterTabItem(T value) : base(value) + { + } + + [BackgroundDependencyLoader] + private void load() { AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomLeft; @@ -32,17 +37,12 @@ namespace osu.Game.Overlays.BeatmapListing text = new OsuSpriteText { Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), - Text = LabelFor(value) + Text = LabelFor(Value) }, new HoverClickSounds() }); Enabled.Value = true; - } - - [BackgroundDependencyLoader] - private void load() - { updateState(); } From 1313ab89e76d0a0f5caaed1274c6a12971a34db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Oct 2020 23:37:21 +0100 Subject: [PATCH 32/63] Add xmldoc to multiple selection row --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index ae669e91dd..5dfa8e6109 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -25,8 +25,11 @@ namespace osu.Game.Overlays.BeatmapListing Current.BindTo(filter.Current); } - protected override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + protected sealed override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); + /// + /// Creates a filter control that can be used to simultaneously select multiple values of type . + /// protected virtual MultipleSelectionFilter CreateMultipleSelectionFilter() => new MultipleSelectionFilter(); protected class MultipleSelectionFilter : FillFlowContainer @@ -53,8 +56,14 @@ namespace osu.Game.Overlays.BeatmapListing item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); } + /// + /// Returns all values to be displayed in this filter row. + /// protected virtual IEnumerable GetValues() => Enum.GetValues(typeof(T)).Cast(); + /// + /// Creates a representing the supplied . + /// protected virtual MultipleSelectionFilterTabItem CreateTabItem(T value) => new MultipleSelectionFilterTabItem(value); private void toggleItem(T value, bool active) From 2e5a8b2287ce1b74f316671d9533faf87cb967f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 13:16:27 +0900 Subject: [PATCH 33/63] Fix xmldoc to read better in new context --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6548bee4ef..595574115c 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -236,14 +236,13 @@ namespace osu.Game.Rulesets.UI NotValid, /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. + /// Playback is running behind real-time. Catch-up will be attempted by processing more than once per + /// game loop (limited to a sane maximum to avoid frame drops). /// RequiresCatchUp, /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// In a valid state, progressing one child hierarchy loop per game loop. /// Valid } From c0960e60cb59a50ace68901ae8a71df26ebc0e7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 14:52:34 +0900 Subject: [PATCH 34/63] Add note about testflight link Sick of getting asked about this. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7c749f3422..86c42dae12 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ If you are looking to install or test osu! without setting up a development envi | [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | ------------- | ------------- | ------------- | ------------- | ------------- | +- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. + - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. From 3751c357a35b82bbc90cefe72eefe1052582f466 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:19:05 +0900 Subject: [PATCH 35/63] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 2d531cf01e..27846fdf53 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ca588b89d9..609ac0e5f9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9c22dec330..ebd38bc334 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 3ea27e23e8b8395b091221a78700f9c4b95aa347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 36/63] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 71e373ff511de83b10282992570165f087741551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 16:11:25 +0900 Subject: [PATCH 37/63] Make results panels aware of whether they are a local score that has just been set --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 +- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 8 ++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/ScorePanel.cs | 7 +++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 5 +++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index 1e87893f39..f69ccc1773 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score) + new AccuracyCircle(score, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..5af55e99f8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = panel = new ScorePanel(score) + Child = panel = new ScorePanel(score, true) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 45da23f1f9..337665b51f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score) + public AccuracyCircle(ScoreInfo score, bool withFlair) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 30747438c3..5f8609d190 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -29,6 +29,8 @@ namespace osu.Game.Screens.Ranking.Expanded private const float padding = 10; private readonly ScoreInfo score; + private readonly bool withFlair; + private readonly List statisticDisplays = new List(); private FillFlowContainer starAndModDisplay; @@ -41,9 +43,11 @@ namespace osu.Game.Screens.Ranking.Expanded /// Creates a new . /// /// The score to display. - public ExpandedPanelMiddleContent(ScoreInfo score) + /// Whether to add flair for a new score being set. + public ExpandedPanelMiddleContent(ScoreInfo score, bool withFlair = false) { this.score = score; + this.withFlair = withFlair; RelativeSizeAxes = Axes.Both; Masking = true; @@ -116,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score) + Child = new AccuracyCircle(score, withFlair) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 026ce01857..f8bdf0140c 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - ScorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score, true); if (player != null && allowRetry) { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index ee97ee55eb..6e6227da38 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,6 +85,8 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; + private readonly bool isNewLocalScore; + private Container content; private Container topLayerContainer; @@ -97,9 +99,10 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; - public ScorePanel(ScoreInfo score) + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; + this.isNewLocalScore = isNewLocalScore; } [BackgroundDependencyLoader] @@ -209,7 +212,7 @@ namespace osu.Game.Screens.Ranking middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); break; case PanelState.Contracted: diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0d7d339df0..cc163ba762 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -95,9 +95,10 @@ namespace osu.Game.Screens.Ranking /// Adds a to this list. /// /// The to add. - public ScorePanel AddScore(ScoreInfo score) + /// Whether this is a score that has just been achieved locally. Controls whether flair is added to the display or not. + public ScorePanel AddScore(ScoreInfo score, bool isNewLocalScore = false) { - var panel = new ScorePanel(score) + var panel = new ScorePanel(score, isNewLocalScore) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 11f85779d5222f24fa9d0edd8097398417407b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:03:45 +0900 Subject: [PATCH 38/63] Fix panel expanded state being updated multiple times unnecessarily --- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc163ba762..e85580a734 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -119,7 +119,10 @@ namespace osu.Game.Screens.Ranking })); if (SelectedScore.Value == score) - selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + { + if (IsLoaded) + SelectedScore.TriggerChange(); + } else { // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. @@ -143,11 +146,15 @@ namespace osu.Game.Screens.Ranking /// The to present. private void selectedScoreChanged(ValueChangedEvent score) { - // Contract the old panel. - foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + // avoid contracting panels unnecessarily when TriggerChange is fired manually. + if (score.OldValue != score.NewValue) { - t.Panel.State = PanelState.Contracted; - t.Margin = new MarginPadding(); + // Contract the old panel. + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) + { + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); + } } // Find the panel corresponding to the new score. From 0a0239a7c799b88049f4a4ca524a58d6f6839a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:04:33 +0900 Subject: [PATCH 39/63] Only play results panel animation once (and only for the local user) --- .../Ranking/Expanded/ExpandedPanelMiddleContent.cs | 3 +++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5f8609d190..711763330c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -270,6 +270,9 @@ namespace osu.Game.Screens.Ranking.Expanded delay += 200; } } + + if (!withFlair) + FinishTransforms(true); }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6e6227da38..df710e4eb8 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -85,7 +85,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private readonly bool isNewLocalScore; + private bool displayWithFlair; private Container content; @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; - this.isNewLocalScore = isNewLocalScore; + displayWithFlair = isNewLocalScore; } [BackgroundDependencyLoader] @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Ranking state = value; - if (LoadState >= LoadState.Ready) + if (IsLoaded) updateState(); StateChanged?.Invoke(value); @@ -212,7 +212,10 @@ namespace osu.Game.Screens.Ranking middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, isNewLocalScore).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + + // only the first expanded display should happen with flair. + displayWithFlair = false; break; case PanelState.Contracted: From 4dec46b33e977e8351f916a2c60c71a53a20b08a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 17:52:58 +0900 Subject: [PATCH 40/63] Attempt to fix in a less destructive way for now --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 8886188d95..e32ed07863 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual if (beatmap.HitObjects.Count > 0) // add buffer after last hitobject to allow for final replay frames etc. - trackLength = beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000; + trackLength = Math.Max(trackLength, beatmap.HitObjects.Max(h => h.GetEndTime()) + 2000); if (referenceClock != null) { From 335d150a134073aff3488a0476eff2d970e5d185 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 18:11:50 +0900 Subject: [PATCH 41/63] Fix aim time being mutated inside update loop --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 231c5110ea..e29abfd83e 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - double proposedTime = manualClock.CurrentTime; + double aimTime = manualClock.CurrentTime; if (frameStableClock.WaitingOnFrames.Value) { @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.UI if (parentGameplayClock == null) setClock(); // LoadComplete may not be run yet, but we still want the clock. - proposedTime = parentGameplayClock.CurrentTime; + aimTime = parentGameplayClock.CurrentTime; } else { @@ -113,7 +113,9 @@ namespace osu.Game.Rulesets.UI while (loops-- > 0) { - updateClock(ref proposedTime); + // update clock is always trying to approach the aim time. + // it should be provided as the original value each loop. + updateClock(aimTime); if (state == PlaybackState.NotValid) break; @@ -125,7 +127,7 @@ namespace osu.Game.Rulesets.UI return true; } - private void updateClock(ref double proposedTime) + private void updateClock(double proposedTime) { // each update start with considering things in valid state. state = PlaybackState.Valid; From d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 15:20:10 +0900 Subject: [PATCH 42/63] Update namespace references --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 + osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index fde42bec04..9bfb6aa839 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 9a0217a1eb..4cadfa9ad4 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 3491dea9e2aece3ab76f0b931a5c9ef599e6eba4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 18:51:54 +0900 Subject: [PATCH 43/63] Fix scroll logic running before children may be alive in flow --- osu.Game/Screens/Ranking/ScorePanelList.cs | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e85580a734..4325d317c4 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -118,22 +118,24 @@ namespace osu.Game.Screens.Ranking d.Origin = Anchor.Centre; })); - if (SelectedScore.Value == score) + if (IsLoaded) { - if (IsLoaded) - SelectedScore.TriggerChange(); - } - else - { - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + if (SelectedScore.Value == score) { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + SelectedScore.TriggerChange(); + } + else + { + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } } @@ -170,12 +172,15 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - // Scroll to the new panel. This is done manually since we need: - // 1) To scroll after the scroll container's visible range is updated. - // 2) To account for the centre anchor/origins of panels. - // In the end, it's easier to compute the scroll position manually. - float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); - scroll.ScrollTo(scrollOffset); + SchedulerAfterChildren.Add(() => + { + // Scroll to the new panel. This is done manually since we need: + // 1) To scroll after the scroll container's visible range is updated. + // 2) To account for the centre anchor/origins of panels. + // In the end, it's easier to compute the scroll position manually. + float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + scroll.ScrollTo(scrollOffset); + }); } protected override void Update() From 7be4dfabd8d384107b5a21a78ae6fa97f09fbeaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:23:15 +0900 Subject: [PATCH 44/63] Revert "Update namespace references" This reverts commit d69d78ab5d9278aaf31d20bd2895f664d1e3c2f1. --- osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs | 1 - osu.Game/Rulesets/UI/HitObjectContainer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs index 9bfb6aa839..fde42bec04 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 4cadfa9ad4..9a0217a1eb 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI From 1c353b4745f1ea2bf2cb1a754d47478b7221c2b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Oct 2020 20:38:28 +0900 Subject: [PATCH 45/63] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 27846fdf53..a4bcbd289d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 609ac0e5f9..9be933c74a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ebd38bc334..e26f8cc8b4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 0c1d12460fcc0304ce1889e21c684da5f107f59d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 10:30:11 +0900 Subject: [PATCH 46/63] Remove unused parameter --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 2 +- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index f69ccc1773..1e87893f39 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking } } }, - new AccuracyCircle(score, false) + new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 337665b51f..45da23f1f9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private Container badges; private RankText rankText; - public AccuracyCircle(ScoreInfo score, bool withFlair) + public AccuracyCircle(ScoreInfo score) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 711763330c..cb4560802b 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Ranking.Expanded Margin = new MarginPadding { Top = 40 }, RelativeSizeAxes = Axes.X, Height = 230, - Child = new AccuracyCircle(score, withFlair) + Child = new AccuracyCircle(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 8e6c803900757b019b8d0db051d445c2b99039c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:39:11 +0900 Subject: [PATCH 47/63] Avoid running full updateClock loop when waiting on frames --- .../Rulesets/UI/FrameStabilityContainer.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 0990a667ec..4d554124ae 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -85,30 +85,29 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - double aimTime = manualClock.CurrentTime; + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + double aimTime = parentGameplayClock.CurrentTime; if (frameStableClock.WaitingOnFrames.Value) { - // when waiting on frames, the update loop still needs to be run (at least once) to check for newly arrived frames. - // time should not be sourced from the parent clock in this case. - state = PlaybackState.Valid; + // waiting on frames is a special case where we want to avoid doing any update propagation, unless new frame data has arrived. + state = ReplayInputHandler.SetFrameFromTime(aimTime) != null ? PlaybackState.Valid : PlaybackState.NotValid; } else if (!frameStableClock.IsPaused.Value) { state = PlaybackState.Valid; - - if (parentGameplayClock == null) - setClock(); // LoadComplete may not be run yet, but we still want the clock. - - aimTime = parentGameplayClock.CurrentTime; } else { - // time should not advance while paused, not should anything run. + // time should not advance while paused, nor should anything run. state = PlaybackState.NotValid; - return true; } + if (state == PlaybackState.NotValid) + return true; + int loops = MaxCatchUpFrames; while (loops-- > 0) From 46d89d55f4e9b33edc97cb3696cf2a9ebcee7727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 12:46:48 +0900 Subject: [PATCH 48/63] Add note about ScheduleAfterChildren requirement --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4325d317c4..77b3d8fc3b 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -172,7 +172,8 @@ namespace osu.Game.Screens.Ranking expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - SchedulerAfterChildren.Add(() => + // requires schedule after children to ensure the flow (and thus ScrollContainer's ScrollableExtent) has been updated. + ScheduleAfterChildren(() => { // Scroll to the new panel. This is done manually since we need: // 1) To scroll after the scroll container's visible range is updated. From 18f92818daed770f18d35fc74cc22c4da392e567 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:13 +0900 Subject: [PATCH 49/63] Show current HUD visibility mode as a tracked setting --- osu.Game/Configuration/OsuConfigManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..46c5e61784 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -170,6 +170,7 @@ namespace osu.Game.Configuration public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), + new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())), new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), }; } From 9bb86ccb832d8f838517c5d4ef7af8d018d2ed38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:09:22 +0900 Subject: [PATCH 50/63] Change shift-tab to cycle available HUD visibility modes --- osu.Game/Screens/Play/HUDOverlay.cs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..623041d9ca 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -277,9 +277,25 @@ namespace osu.Game.Screens.Play switch (e.Key) { case Key.Tab: - configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never - ? HUDVisibilityMode.Never - : HUDVisibilityMode.HideDuringGameplay; + switch (configVisibilityMode.Value) + { + case HUDVisibilityMode.Never: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringGameplay; + break; + + case HUDVisibilityMode.HideDuringGameplay: + configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; + break; + + case HUDVisibilityMode.HideDuringBreaks: + configVisibilityMode.Value = HUDVisibilityMode.Always; + break; + + case HUDVisibilityMode.Always: + configVisibilityMode.Value = HUDVisibilityMode.Never; + break; + } + return true; } } From c72017a7db4ad00ba2b63d976fca20c5ea9ac583 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:49:44 +0900 Subject: [PATCH 51/63] Remove "hide during breaks" option Probably wouldn't be used anyway. --- osu.Game/Configuration/HUDVisibilityMode.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs index b0b55dd811..10f3f65355 100644 --- a/osu.Game/Configuration/HUDVisibilityMode.cs +++ b/osu.Game/Configuration/HUDVisibilityMode.cs @@ -12,9 +12,6 @@ namespace osu.Game.Configuration [Description("Hide during gameplay")] HideDuringGameplay, - [Description("Hide during breaks")] - HideDuringBreaks, - Always } } From b4eda65383cf80c56ac4991887836a12bb5a5be8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 13:53:51 +0900 Subject: [PATCH 52/63] Commit missing pieces --- osu.Game/Screens/Play/HUDOverlay.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 623041d9ca..0cfe6effc1 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -223,11 +223,6 @@ namespace osu.Game.Screens.Play ShowHud.Value = false; break; - case HUDVisibilityMode.HideDuringBreaks: - // always show during replay as we want the seek bar to be visible. - ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value; - break; - case HUDVisibilityMode.HideDuringGameplay: // always show during replay as we want the seek bar to be visible. ShowHud.Value = replayLoaded.Value || IsBreakTime.Value; @@ -284,10 +279,6 @@ namespace osu.Game.Screens.Play break; case HUDVisibilityMode.HideDuringGameplay: - configVisibilityMode.Value = HUDVisibilityMode.HideDuringBreaks; - break; - - case HUDVisibilityMode.HideDuringBreaks: configVisibilityMode.Value = HUDVisibilityMode.Always; break; From 53bd31c69e6acada773346e350ffc12430ae651d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:00:07 +0900 Subject: [PATCH 53/63] Commit missing test pieces --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..6764501569 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExternalHideDoesntAffectConfig() { - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; createNew(); From 8928aa6d92990ce761c205e80ac3c20b1a4feffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:19:40 +0900 Subject: [PATCH 54/63] Add key binding to show HUD while held --- .../Input/Bindings/GlobalActionContainer.cs | 4 +++ osu.Game/Screens/Play/HUDOverlay.cs | 36 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 41be4cfcc3..3de4bb1f9d 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -67,6 +67,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), + new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), }; public IEnumerable AudioControlKeyBindings => new[] @@ -187,5 +188,8 @@ namespace osu.Game.Input.Bindings [Description("Timing Mode")] EditorTimingMode, + + [Description("Hold for HUD")] + HoldForHUD, } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b047d44f8a..c38c2ee5f7 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -8,8 +8,10 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; @@ -22,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Screens.Play { [Cached] - public class HUDOverlay : Container + public class HUDOverlay : Container, IKeyBindingHandler { public const float FADE_DURATION = 400; @@ -67,6 +69,8 @@ namespace osu.Game.Screens.Play internal readonly IBindable IsBreakTime = new Bindable(); + private bool holdingForHUD; + private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter }; public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) @@ -217,6 +221,12 @@ namespace osu.Game.Screens.Play if (ShowHud.Disabled) return; + if (holdingForHUD) + { + ShowHud.Value = true; + return; + } + switch (configVisibilityMode.Value) { case HUDVisibilityMode.Never: @@ -351,5 +361,29 @@ namespace osu.Game.Screens.Play HealthDisplay?.BindHealthProcessor(processor); FailingLayer?.BindHealthProcessor(processor); } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = true; + updateVisibility(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.HoldForHUD: + holdingForHUD = false; + updateVisibility(); + break; + } + } } } From bd7871d9f511b09ddd224759e3b9365f043c27d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:20:00 +0900 Subject: [PATCH 55/63] Update test scene to be non-skinnable (and add test covering momentary display) --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 66 ++++++++++--------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 6ec673704c..136c9e191d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -2,29 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHUDOverlay : SkinnableTestScene + public class TestSceneHUDOverlay : OsuManualInputManagerTestScene { private HUDOverlay hudOverlay; - private IEnumerable hudOverlays => CreatedDrawables.OfType(); - // best way to check without exposing. private Drawable hideTarget => hudOverlay.KeyCounter; private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); @@ -37,17 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddRepeatStep("increase combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value++; - }, 10); + AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10); - AddStep("reset combo", () => - { - foreach (var hud in hudOverlays) - hud.ComboCounter.Current.Value = 0; - }); + AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; }); } [Test] @@ -77,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); @@ -86,6 +72,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); } + [Test] + public void TestMomentaryShowHUD() + { + createNew(); + + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); + + AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); + + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft)); + AddUntilStep("wait for visible", () => hideTarget.IsPresent); + + AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); + AddUntilStep("wait for fade", () => !hideTarget.IsPresent); + + AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue)); + } + [Test] public void TestExternalHideDoesntAffectConfig() { @@ -113,14 +120,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set keycounter visible false", () => { config.Set(OsuSetting.KeyOverlay, false); - hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false); + hudOverlay.KeyCounter.AlwaysVisible.Value = false; }); - AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); - AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true)); + AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); @@ -131,22 +138,17 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create overlay", () => { - SetContents(() => - { - hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); - // Add any key just to display the key counter visually. - hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); - hudOverlay.ComboCounter.Current.Value = 1; + hudOverlay.ComboCounter.Current.Value = 1; - action?.Invoke(hudOverlay); + action?.Invoke(hudOverlay); - return hudOverlay; - }); + Child = hudOverlay; }); } - - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } From 984a243eff796505ee9f2ca6e85ae2a00233e3a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 14:23:24 +0900 Subject: [PATCH 56/63] Add skinnable test scene for HUD overlay --- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs new file mode 100644 index 0000000000..fec1610160 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -0,0 +1,99 @@ +// 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 System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinnableHUDOverlay : SkinnableTestScene + { + private HUDOverlay hudOverlay; + + private IEnumerable hudOverlays => CreatedDrawables.OfType(); + + // best way to check without exposing. + private Drawable hideTarget => hudOverlay.KeyCounter; + private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First(); + + [Resolved] + private OsuConfigManager config { get; set; } + + [Test] + public void TestComboCounterIncrementing() + { + createNew(); + + AddRepeatStep("increase combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value++; + }, 10); + + AddStep("reset combo", () => + { + foreach (var hud in hudOverlays) + hud.ComboCounter.Current.Value = 0; + }); + } + + [Test] + public void TestFadesInOnLoadComplete() + { + float? initialAlpha = null; + + createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); + AddUntilStep("wait for load", () => hudOverlay.IsAlive); + AddAssert("initial alpha was less than 1", () => initialAlpha < 1); + } + + [Test] + public void TestHideExternally() + { + createNew(); + + AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); + + AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); + AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); + + // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. + AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); + } + + private void createNew(Action action = null) + { + AddStep("create overlay", () => + { + SetContents(() => + { + hudOverlay = new HUDOverlay(null, null, null, Array.Empty()); + + // Add any key just to display the key counter visually. + hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space)); + + hudOverlay.ComboCounter.Current.Value = 1; + + action?.Invoke(hudOverlay); + + return hudOverlay; + }); + }); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + } +} From 326fd0352568770e0cd0c494863ede623a977a73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 15:25:53 +0900 Subject: [PATCH 57/63] Fix loop not exiting after first valid frame --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4d554124ae..1ff8fc9715 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.UI int loops = MaxCatchUpFrames; - while (loops-- > 0) + do { // update clock is always trying to approach the aim time. // it should be provided as the original value each loop. @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.UI base.UpdateSubTree(); UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - } + } while (state == PlaybackState.RequiresCatchUp && loops-- > 0); return true; } From 0f997386aef703dc440d9afb0e1ee6e0227304ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 15:26:21 +0900 Subject: [PATCH 58/63] Fix direction and IsRunning not updating on first frame after becoming valid The parent clock will not unpause until WaitingForFrames becomes false, so I've moved the set of that before we start to propagate its values across. Doesn't fix any visible issue but should make propagation one game loop faster. --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 1ff8fc9715..8a7f8d2739 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -144,22 +144,22 @@ namespace osu.Game.Rulesets.UI state = PlaybackState.NotValid; } - if (proposedTime != manualClock.CurrentTime) + if (state == PlaybackState.Valid) direction = proposedTime >= manualClock.CurrentTime ? 1 : -1; + double timeBehind = Math.Abs(proposedTime - parentGameplayClock.CurrentTime); + + frameStableClock.IsCatchingUp.Value = timeBehind > 200; + frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid; + manualClock.CurrentTime = proposedTime; manualClock.Rate = Math.Abs(parentGameplayClock.Rate) * direction; manualClock.IsRunning = parentGameplayClock.IsRunning; - double timeBehind = Math.Abs(manualClock.CurrentTime - parentGameplayClock.CurrentTime); - // determine whether catch-up is required. if (state == PlaybackState.Valid && timeBehind > 0) state = PlaybackState.RequiresCatchUp; - frameStableClock.IsCatchingUp.Value = timeBehind > 200; - frameStableClock.WaitingOnFrames.Value = state == PlaybackState.NotValid; - // The manual clock time has changed in the above code. The framed clock now needs to be updated // to ensure that the its time is valid for our children before input is processed framedClock.ProcessFrame(); From 32e68a6a3c16df655a597b1b3f770c77d2dec2cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 16:09:01 +0900 Subject: [PATCH 59/63] Fix FramedReplayInputHandler incorrectly blocking in streaming mode when time requested is before the first frame Most of this is just tidying up the logic to (hopefully) be better to follow, again (again (again)). The actual fix is that we now allow interpolation/playback when the incoming time is less than the first frame's time, regardless of receiving status. --- .../Replays/FramedReplayInputHandler.cs | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index 8a4451fdca..b43324bcfa 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Replays return null; if (!currentFrameIndex.HasValue) - return (TFrame)Frames[0]; + return currentDirection > 0 ? (TFrame)Frames[0] : null; int nextFrame = clampedNextFrameIndex; @@ -109,30 +109,54 @@ namespace osu.Game.Rulesets.Replays Debug.Assert(currentDirection != 0); - TFrame next = NextFrame; - - // check if the next frame is valid for the current playback direction. - // validity is if the next frame is equal or "earlier" than the current point in time (so we can change to it) - int compare = time.CompareTo(next?.Time); - - if (next != null && (compare == 0 || compare == currentDirection)) + if (!HasFrames) { - currentFrameIndex = clampedNextFrameIndex; - return CurrentTime = CurrentFrame.Time; + // in the case all frames are received, allow time to progress regardless. + if (replay.HasReceivedAllFrames) + return CurrentTime = time; + + return null; } - // at this point, the frame can't be advanced (in the replay). - // even so, we may be able to move the clock forward due to being at the end of the replay or - // moving towards the next valid frame. + TFrame next = NextFrame; + + // if we have a next frame, check if it is before or at the current time in playback, and advance time to it if so. + if (next != null) + { + int compare = time.CompareTo(next.Time); + + if (compare == 0 || compare == currentDirection) + { + currentFrameIndex = clampedNextFrameIndex; + return CurrentTime = CurrentFrame.Time; + } + } + + // at this point, the frame index can't be advanced. + // even so, we may be able to propose the clock progresses forward due to being at an extent of the replay, + // or moving towards the next valid frame (ie. interpolating in a non-important section). // the exception is if currently in an important section, which is respected above all. if (inImportantSection) + { + Debug.Assert(next != null || !replay.HasReceivedAllFrames); return null; + } - // in the case we have no next frames and haven't received the full replay, block. - if (next == null && !replay.HasReceivedAllFrames) return null; + // if a next frame does exist, allow interpolation. + if (next != null) + return CurrentTime = time; - return CurrentTime = time; + // if all frames have been received, allow playing beyond extents. + if (replay.HasReceivedAllFrames) + return CurrentTime = time; + + // if not all frames are received but we are before the first frame, allow playing. + if (time < Frames[0].Time) + return CurrentTime = time; + + // in the case we have no next frames and haven't received enough frame data, block. + return null; } private void updateDirection(double time) From abaa532766500c71a2dbf5054019dc1b1f7de2be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 16:24:02 +0900 Subject: [PATCH 60/63] Add test coverage for streaming replay playback --- .../StreamingFramedReplayInputHandlerTest.cs | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs diff --git a/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs new file mode 100644 index 0000000000..21ec29b10b --- /dev/null +++ b/osu.Game.Tests/NonVisual/StreamingFramedReplayInputHandlerTest.cs @@ -0,0 +1,296 @@ +// 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.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class StreamingFramedReplayInputHandlerTest + { + private Replay replay; + private TestInputHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestInputHandler(replay = new Replay + { + HasReceivedAllFrames = false, + Frames = new List + { + new TestReplayFrame(0), + new TestReplayFrame(1000), + new TestReplayFrame(2000), + new TestReplayFrame(3000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(5000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(8000), + } + }); + } + + [Test] + public void TestNormalPlayback() + { + Assert.IsNull(handler.CurrentFrame); + + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + + // if we hit the first frame perfectly, time should progress to it. + setTime(1000, 1000); + confirmCurrentFrame(1); + confirmNextFrame(2); + + // in between non-important frames should progress based on input. + setTime(1200, 1200); + confirmCurrentFrame(1); + + setTime(1400, 1400); + confirmCurrentFrame(1); + + // progressing beyond the next frame should force time to that frame once. + setTime(2200, 2000); + confirmCurrentFrame(2); + + // second attempt should progress to input time + setTime(2200, 2200); + confirmCurrentFrame(2); + + // entering important section + setTime(3000, 3000); + confirmCurrentFrame(3); + + // cannot progress within + setTime(3500, null); + confirmCurrentFrame(3); + + setTime(4000, 4000); + confirmCurrentFrame(4); + + // still cannot progress + setTime(4500, null); + confirmCurrentFrame(4); + + setTime(5200, 5000); + confirmCurrentFrame(5); + + // important section AllowedImportantTimeSpan allowance + setTime(5200, 5200); + confirmCurrentFrame(5); + + setTime(7200, 7000); + confirmCurrentFrame(6); + + setTime(7200, null); + confirmCurrentFrame(6); + + // exited important section + setTime(8200, 8000); + confirmCurrentFrame(7); + confirmNextFrame(null); + + setTime(8200, null); + confirmCurrentFrame(7); + confirmNextFrame(null); + + setTime(8400, null); + confirmCurrentFrame(7); + confirmNextFrame(null); + } + + [Test] + public void TestIntroTime() + { + setTime(-1000, -1000); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(-500, -500); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + } + + [Test] + public void TestBasicRewind() + { + setTime(2800, 0); + setTime(2800, 1000); + setTime(2800, 2000); + setTime(2800, 2800); + confirmCurrentFrame(2); + confirmNextFrame(3); + + // pivot without crossing a frame boundary + setTime(2700, 2700); + confirmCurrentFrame(2); + confirmNextFrame(1); + + // cross current frame boundary; should not yet update frame + setTime(1980, 1980); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(1200, 1200); + confirmCurrentFrame(2); + confirmNextFrame(1); + + // ensure each frame plays out until start + setTime(-500, 1000); + confirmCurrentFrame(1); + confirmNextFrame(0); + + setTime(-500, 0); + confirmCurrentFrame(0); + confirmNextFrame(null); + + setTime(-500, -500); + confirmCurrentFrame(0); + confirmNextFrame(null); + } + + [Test] + public void TestRewindInsideImportantSection() + { + fastForwardToPoint(3000); + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + + setTime(3500, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4500, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4000, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + [Test] + public void TestRewindOutOfImportantSection() + { + fastForwardToPoint(3500); + + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3200, null); + // next frame doesn't change even though direction reversed, because of important section. + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3000, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(2800, 2800); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + private void fastForwardToPoint(double destination) + { + for (int i = 0; i < 1000; i++) + { + if (handler.SetFrameFromTime(destination) == null) + return; + } + + throw new TimeoutException("Seek was never fulfilled"); + } + + private void setTime(double set, double? expect) + { + Assert.AreEqual(expect, handler.SetFrameFromTime(set)); + } + + private void confirmCurrentFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.CurrentFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); + } + else + { + Assert.IsNull(handler.CurrentFrame); + } + } + + private void confirmNextFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.NextFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); + } + else + { + Assert.IsNull(handler.NextFrame); + } + } + + private class TestReplayFrame : ReplayFrame + { + public readonly bool IsImportant; + + public TestReplayFrame(double time, bool isImportant = false) + : base(time) + { + IsImportant = isImportant; + } + } + + private class TestInputHandler : FramedReplayInputHandler + { + public TestInputHandler(Replay replay) + : base(replay) + { + FrameAccuratePlayback = true; + } + + protected override double AllowedImportantTimeSpan => 1000; + + protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant; + } + } +} From 43f9c1ebead2fb96e7f5994868ce43c93133d3ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Oct 2020 18:26:38 +0900 Subject: [PATCH 61/63] Fix HUD test having out of date value --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 9744575878..f9914e0193 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -77,7 +77,8 @@ namespace osu.Game.Tests.Visual.Gameplay { createNew(); - HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks; + HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay; + AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode)); AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); From 1bd461f229a69dc139fc7c36e4f10cb5a874243c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 20:20:34 +0900 Subject: [PATCH 62/63] Move clock logic back to inside updateClock method --- .../Rulesets/UI/FrameStabilityContainer.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 8a7f8d2739..c8f37d75a0 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -85,15 +85,10 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - if (parentGameplayClock == null) - setClock(); // LoadComplete may not be run yet, but we still want the clock. - - double aimTime = parentGameplayClock.CurrentTime; - if (frameStableClock.WaitingOnFrames.Value) { // waiting on frames is a special case where we want to avoid doing any update propagation, unless new frame data has arrived. - state = ReplayInputHandler.SetFrameFromTime(aimTime) != null ? PlaybackState.Valid : PlaybackState.NotValid; + state = PlaybackState.Valid; } else if (!frameStableClock.IsPaused.Value) { @@ -103,10 +98,8 @@ namespace osu.Game.Rulesets.UI { // time should not advance while paused, nor should anything run. state = PlaybackState.NotValid; - } - - if (state == PlaybackState.NotValid) return true; + } int loops = MaxCatchUpFrames; @@ -114,7 +107,7 @@ namespace osu.Game.Rulesets.UI { // update clock is always trying to approach the aim time. // it should be provided as the original value each loop. - updateClock(aimTime); + updateClock(); if (state == PlaybackState.NotValid) break; @@ -126,8 +119,13 @@ namespace osu.Game.Rulesets.UI return true; } - private void updateClock(double proposedTime) + private void updateClock() { + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + double proposedTime = parentGameplayClock.CurrentTime; + // each update start with considering things in valid state. state = PlaybackState.Valid; From b4e53110146b4f7d651cd588d794be0d075e4db1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Oct 2020 20:37:07 +0900 Subject: [PATCH 63/63] Move initial state set inside updateClock --- .../Rulesets/UI/FrameStabilityContainer.cs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c8f37d75a0..e9865f6c8b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -85,22 +85,6 @@ namespace osu.Game.Rulesets.UI public override bool UpdateSubTree() { - if (frameStableClock.WaitingOnFrames.Value) - { - // waiting on frames is a special case where we want to avoid doing any update propagation, unless new frame data has arrived. - state = PlaybackState.Valid; - } - else if (!frameStableClock.IsPaused.Value) - { - state = PlaybackState.Valid; - } - else - { - // time should not advance while paused, nor should anything run. - state = PlaybackState.NotValid; - return true; - } - int loops = MaxCatchUpFrames; do @@ -121,14 +105,27 @@ namespace osu.Game.Rulesets.UI private void updateClock() { + if (frameStableClock.WaitingOnFrames.Value) + { + // if waiting on frames, run one update loop to determine if frames have arrived. + state = PlaybackState.Valid; + } + else if (frameStableClock.IsPaused.Value) + { + // time should not advance while paused, nor should anything run. + state = PlaybackState.NotValid; + return; + } + else + { + state = PlaybackState.Valid; + } + if (parentGameplayClock == null) setClock(); // LoadComplete may not be run yet, but we still want the clock. double proposedTime = parentGameplayClock.CurrentTime; - // each update start with considering things in valid state. - state = PlaybackState.Valid; - if (FrameStablePlayback) // if we require frame stability, the proposed time will be adjusted to move at most one known // frame interval in the current direction.