From a3fd952f74bf6693a576f3db4b8261e399db037c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 11:21:23 +0300 Subject: [PATCH 01/52] Use new SpotlightSelector in RankingsHeader --- .../Visual/Online/TestSceneRankingsHeader.cs | 13 ++-- .../Rankings/RankingsOverlayHeader.cs | 61 +++---------------- osu.Game/Overlays/Rankings/Spotlight.cs | 18 ------ 3 files changed, 16 insertions(+), 76 deletions(-) delete mode 100644 osu.Game/Overlays/Rankings/Spotlight.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index 898e461bde..bc0ae3d264 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Rankings; using osu.Game.Rulesets; @@ -37,20 +38,20 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset }, Spotlights = new[] { - new Spotlight + new APISpotlight { Id = 1, - Text = "Spotlight 1" + Name = "Spotlight 1" }, - new Spotlight + new APISpotlight { Id = 2, - Text = "Spotlight 2" + Name = "Spotlight 2" }, - new Spotlight + new APISpotlight { Id = 3, - Text = "Spotlight 3" + Name = "Spotlight 3" } } }); diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 94afe4e5a5..41722034b6 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -8,21 +8,20 @@ using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Allocation; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Rankings { public class RankingsOverlayHeader : TabControlOverlayHeader { public readonly Bindable Ruleset = new Bindable(); - public readonly Bindable Spotlight = new Bindable(); + public readonly Bindable Spotlight = new Bindable(); public readonly Bindable Country = new Bindable(); - public IEnumerable Spotlights + public IEnumerable Spotlights { - get => spotlightsContainer.Spotlights; - set => spotlightsContainer.Spotlights = value; + get => spotlightSelector.Spotlights; + set => spotlightSelector.Spotlights = value; } protected override ScreenTitle CreateTitle() => new RankingsTitle @@ -35,7 +34,7 @@ namespace osu.Game.Overlays.Rankings Current = Ruleset }; - private SpotlightsContainer spotlightsContainer; + private SpotlightSelector spotlightSelector; protected override Drawable CreateContent() => new FillFlowContainer { @@ -48,9 +47,9 @@ namespace osu.Game.Overlays.Rankings { Current = Country }, - spotlightsContainer = new SpotlightsContainer + spotlightSelector = new SpotlightSelector { - Spotlight = { BindTarget = Spotlight } + Current = { BindTarget = Spotlight } } } }; @@ -62,7 +61,7 @@ namespace osu.Game.Overlays.Rankings } private void onCurrentChanged(ValueChangedEvent scope) => - spotlightsContainer.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); + spotlightSelector.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); private class RankingsTitle : ScreenTitle { @@ -81,48 +80,6 @@ namespace osu.Game.Overlays.Rankings protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings"); } - - private class SpotlightsContainer : CompositeDrawable - { - public readonly Bindable Spotlight = new Bindable(); - - public IEnumerable Spotlights - { - get => dropdown.Items; - set => dropdown.Items = value; - } - - private readonly OsuDropdown dropdown; - private readonly Box background; - - public SpotlightsContainer() - { - Height = 100; - RelativeSizeAxes = Axes.X; - InternalChildren = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - dropdown = new OsuDropdown - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Width = 0.8f, - Current = Spotlight, - Y = 20, - } - }; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - background.Colour = colourProvider.Dark3; - } - } } public enum RankingsScope diff --git a/osu.Game/Overlays/Rankings/Spotlight.cs b/osu.Game/Overlays/Rankings/Spotlight.cs deleted file mode 100644 index e956b4f449..0000000000 --- a/osu.Game/Overlays/Rankings/Spotlight.cs +++ /dev/null @@ -1,18 +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 Newtonsoft.Json; - -namespace osu.Game.Overlays.Rankings -{ - public class Spotlight - { - [JsonProperty("id")] - public int Id; - - [JsonProperty("text")] - public string Text; - - public override string ToString() => Text; - } -} From fa65e3a5bb51db63dc33f74189607e5d8dcb0487 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 12:09:32 +0300 Subject: [PATCH 02/52] Make spotlight selector work --- osu.Game/Overlays/RankingsOverlay.cs | 32 +++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 84470d9caa..36f5a3155a 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -14,6 +14,8 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; +using osu.Game.Online.API.Requests.Responses; +using System.Linq; namespace osu.Game.Overlays { @@ -22,11 +24,13 @@ namespace osu.Game.Overlays protected readonly Bindable Country = new Bindable(); protected readonly Bindable Scope = new Bindable(); private readonly Bindable ruleset = new Bindable(); + private readonly Bindable spotlight = new Bindable(); private readonly BasicScrollContainer scrollFlow; private readonly Container tableContainer; private readonly DimmedLoadingLayer loading; private readonly Box background; + private readonly RankingsOverlayHeader header; private APIRequest lastRequest; private CancellationTokenSource cancellationToken; @@ -54,14 +58,15 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - new RankingsOverlayHeader + header = new RankingsOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Depth = -float.MaxValue, Country = { BindTarget = Country }, Current = { BindTarget = Scope }, - Ruleset = { BindTarget = ruleset } + Ruleset = { BindTarget = ruleset }, + Spotlight = { BindTarget = spotlight } }, new Container { @@ -109,11 +114,19 @@ namespace osu.Game.Overlays if (Scope.Value != RankingsScope.Performance) Country.Value = null; + if (Scope.Value == RankingsScope.Spotlights && !header.Spotlights.Any()) + { + getSpotlights(); + return; + } + Scheduler.AddOnce(loadNewContent); }, true); ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + spotlight.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + base.LoadComplete(); } @@ -127,6 +140,14 @@ namespace osu.Game.Overlays Country.Value = requested; } + private void getSpotlights() + { + loading.Show(); + var request = new GetSpotlightsRequest(); + request.Success += response => header.Spotlights = response.Spotlights; + api.Queue(request); + } + private void loadNewContent() { loading.Show(); @@ -145,7 +166,6 @@ namespace osu.Game.Overlays request.Success += () => loadTable(createTableFromResponse(request)); request.Failure += _ => loadTable(null); - api.Queue(request); } @@ -161,6 +181,9 @@ namespace osu.Game.Overlays case RankingsScope.Score: return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); + + case RankingsScope.Spotlights: + return new GetSpotlightRankingsRequest(ruleset.Value, header.Spotlight.Value.Id); } return null; @@ -184,6 +207,9 @@ namespace osu.Game.Overlays case GetCountryRankingsRequest countryRequest: return new CountriesTable(1, countryRequest.Result.Countries); + + case GetSpotlightRankingsRequest spotlightRequest: + return new ScoresTable(1, spotlightRequest.Result.Users); } return null; From 2b0b789980a0c7a099bc4ae88a5cf11a2229b70e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 12:14:24 +0300 Subject: [PATCH 03/52] Naming adjustments --- osu.Game/Overlays/RankingsOverlay.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 36f5a3155a..39f106a24e 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays private readonly Bindable spotlight = new Bindable(); private readonly BasicScrollContainer scrollFlow; - private readonly Container tableContainer; + private readonly Container contentContainer; private readonly DimmedLoadingLayer loading; private readonly Box background; private readonly RankingsOverlayHeader header; @@ -74,7 +74,7 @@ namespace osu.Game.Overlays AutoSizeAxes = Axes.Y, Children = new Drawable[] { - tableContainer = new Container + contentContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -160,12 +160,12 @@ namespace osu.Game.Overlays if (request == null) { - loadTable(null); + loadContent(null); return; } - request.Success += () => loadTable(createTableFromResponse(request)); - request.Failure += _ => loadTable(null); + request.Success += () => loadContent(createContentFromResponse(request)); + request.Failure += _ => loadContent(null); api.Queue(request); } @@ -189,7 +189,7 @@ namespace osu.Game.Overlays return null; } - private Drawable createTableFromResponse(APIRequest request) + private Drawable createContentFromResponse(APIRequest request) { switch (request) { @@ -215,21 +215,21 @@ namespace osu.Game.Overlays return null; } - private void loadTable(Drawable table) + private void loadContent(Drawable content) { scrollFlow.ScrollToStart(); - if (table == null) + if (content == null) { - tableContainer.Clear(); + contentContainer.Clear(); loading.Hide(); return; } - LoadComponentAsync(table, t => + LoadComponentAsync(content, t => { loading.Hide(); - tableContainer.Child = table; + contentContainer.Child = content; }, (cancellationToken = new CancellationTokenSource()).Token); } } From b83ee6dabf45a4baf1ceebd4204ad56affe1245b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 12:22:42 +0300 Subject: [PATCH 04/52] Show beatmap panels for selected spotlight --- osu.Game/Overlays/RankingsOverlay.cs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 39f106a24e..2a4bc28b18 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -16,6 +16,8 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; using osu.Game.Online.API.Requests.Responses; using System.Linq; +using osuTK; +using osu.Game.Overlays.Direct; namespace osu.Game.Overlays { @@ -38,6 +40,9 @@ namespace osu.Game.Overlays [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private RulesetStore rulesets { get; set; } + public RankingsOverlay() : base(OverlayColourScheme.Green) { @@ -209,7 +214,28 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Result.Countries); case GetSpotlightRankingsRequest spotlightRequest: - return new ScoresTable(1, spotlightRequest.Result.Users); + return new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new ScoresTable(1, spotlightRequest.Result.Users), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(10), + Children = spotlightRequest.Result.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }).ToList() + } + } + }; } return null; From cb30f463fb670d801a54044c6326f7b96e8a73ac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 12:48:29 +0300 Subject: [PATCH 05/52] Update spotlight info based on selected one --- .../API/Requests/Responses/APISpotlight.cs | 3 +++ .../Rankings/RankingsOverlayHeader.cs | 10 +++++----- .../Overlays/Rankings/SpotlightSelector.cs | 19 +++++++++---------- osu.Game/Overlays/RankingsOverlay.cs | 10 +++++++--- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APISpotlight.cs b/osu.Game/Online/API/Requests/Responses/APISpotlight.cs index 3a002e57b2..4f63ebe3df 100644 --- a/osu.Game/Online/API/Requests/Responses/APISpotlight.cs +++ b/osu.Game/Online/API/Requests/Responses/APISpotlight.cs @@ -26,6 +26,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"end_date")] public DateTimeOffset EndDate; + [JsonProperty(@"participant_count")] + public int? Participants; + public override string ToString() => Name; } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 41722034b6..5c78879b26 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -20,8 +20,8 @@ namespace osu.Game.Overlays.Rankings public IEnumerable Spotlights { - get => spotlightSelector.Spotlights; - set => spotlightSelector.Spotlights = value; + get => SpotlightSelector.Spotlights; + set => SpotlightSelector.Spotlights = value; } protected override ScreenTitle CreateTitle() => new RankingsTitle @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Rankings Current = Ruleset }; - private SpotlightSelector spotlightSelector; + public SpotlightSelector SpotlightSelector; protected override Drawable CreateContent() => new FillFlowContainer { @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Rankings { Current = Country }, - spotlightSelector = new SpotlightSelector + SpotlightSelector = new SpotlightSelector { Current = { BindTarget = Spotlight } } @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Rankings } private void onCurrentChanged(ValueChangedEvent scope) => - spotlightSelector.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); + SpotlightSelector.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); private class RankingsTitle : ScreenTitle { diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index e34c01113e..bd2b7d9b21 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -38,6 +38,8 @@ namespace osu.Game.Overlays.Rankings private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; + private readonly InfoColumn mapCountColumn; + private readonly InfoColumn participantsColumn; public SpotlightSelector() { @@ -75,6 +77,8 @@ namespace osu.Game.Overlays.Rankings { startDateColumn = new InfoColumn(@"Start Date"), endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") } } } @@ -88,17 +92,12 @@ namespace osu.Game.Overlays.Rankings background.Colour = colourProvider.Dark3; } - protected override void LoadComplete() + public void ShowInfo(APISpotlight spotlight, int mapCount) { - base.LoadComplete(); - - Current.BindValueChanged(onCurrentChanged); - } - - private void onCurrentChanged(ValueChangedEvent spotlight) - { - startDateColumn.Value = dateToString(spotlight.NewValue.StartDate); - endDateColumn.Value = dateToString(spotlight.NewValue.EndDate); + startDateColumn.Value = dateToString(spotlight.StartDate); + endDateColumn.Value = dateToString(spotlight.EndDate); + mapCountColumn.Value = mapCount.ToString(); + participantsColumn.Value = spotlight.Participants?.ToString("N0"); } private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 2a4bc28b18..ba0cc29d6d 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -36,6 +36,7 @@ namespace osu.Game.Overlays private APIRequest lastRequest; private CancellationTokenSource cancellationToken; + private GetSpotlightsRequest spotlightsRequest; [Resolved] private IAPIProvider api { get; set; } @@ -115,6 +116,8 @@ namespace osu.Game.Overlays Scope.BindValueChanged(_ => { + spotlightsRequest?.Cancel(); + // country filtering is only valid for performance scope. if (Scope.Value != RankingsScope.Performance) Country.Value = null; @@ -148,9 +151,9 @@ namespace osu.Game.Overlays private void getSpotlights() { loading.Show(); - var request = new GetSpotlightsRequest(); - request.Success += response => header.Spotlights = response.Spotlights; - api.Queue(request); + spotlightsRequest = new GetSpotlightsRequest(); + spotlightsRequest.Success += response => header.Spotlights = response.Spotlights; + api.Queue(spotlightsRequest); } private void loadNewContent() @@ -214,6 +217,7 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Result.Countries); case GetSpotlightRankingsRequest spotlightRequest: + header.SpotlightSelector.ShowInfo(spotlightRequest.Result.Spotlight, spotlightRequest.Result.BeatmapSets.Count); return new FillFlowContainer { AutoSizeAxes = Axes.Y, From 6708e271acd04336255c8c3aefa3e3ea8c2f17e7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 13:01:50 +0300 Subject: [PATCH 06/52] Adjust SpotlightSelector animations --- .../Rankings/RankingsOverlayHeader.cs | 2 +- .../Overlays/Rankings/SpotlightSelector.cs | 89 ++++++++++++------- 2 files changed, 56 insertions(+), 35 deletions(-) diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 5c78879b26..538ae112b5 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Rankings } private void onCurrentChanged(ValueChangedEvent scope) => - SpotlightSelector.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint); + SpotlightSelector.State.Value = scope.NewValue == RankingsScope.Spotlights ? Visibility.Visible : Visibility.Hidden; private class RankingsTitle : ScreenTitle { diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index bd2b7d9b21..8af6c0bb3b 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -17,8 +17,11 @@ using osu.Framework.Graphics.UserInterface; namespace osu.Game.Overlays.Rankings { - public class SpotlightSelector : CompositeDrawable, IHasCurrentValue + public class SpotlightSelector : VisibilityContainer, IHasCurrentValue { + private const int height = 100; + private const int duration = 200; + private readonly Box background; private readonly SpotlightsDropdown dropdown; @@ -36,54 +39,60 @@ namespace osu.Game.Overlays.Rankings set => dropdown.Items = value; } + protected override bool StartHidden => true; + private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; private readonly InfoColumn mapCountColumn; private readonly InfoColumn participantsColumn; + private readonly Container content; public SpotlightSelector() { RelativeSizeAxes = Axes.X; - Height = 100; - - InternalChildren = new Drawable[] + Add(content = new Container { - background = new Box + Height = height, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Children = new Drawable[] + background = new Box { - dropdown = new SpotlightsDropdown + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Current = Current, - Depth = -float.MaxValue - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new Drawable[] + dropdown = new SpotlightsDropdown { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Current = Current, + Depth = -float.MaxValue + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Children = new Drawable[] + { + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") + } } } - } - }, - }; + }, + } + }); } [BackgroundDependencyLoader] @@ -100,6 +109,18 @@ namespace osu.Game.Overlays.Rankings participantsColumn.Value = spotlight.Participants?.ToString("N0"); } + protected override void PopIn() + { + this.ResizeHeightTo(height, duration, Easing.OutQuint); + content.FadeIn(duration, Easing.OutQuint); + } + + protected override void PopOut() + { + this.ResizeHeightTo(0, duration, Easing.OutQuint); + content.FadeOut(duration, Easing.OutQuint); + } + private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private class InfoColumn : FillFlowContainer From 00e010a06151a085636dcdfa5fbae2167470780e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 13:11:02 +0300 Subject: [PATCH 07/52] Add visibility test for SpotlightSelector --- .../Visual/Online/TestSceneRankingsSpotlightSelector.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs index e46c8a4a71..f27ab1e775 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs @@ -35,6 +35,12 @@ namespace osu.Game.Tests.Visual.Online Add(selector = new SpotlightSelector()); } + [Test] + public void TestVisibility() + { + AddStep("Toggle Visibility", selector.ToggleVisibility); + } + [Test] public void TestLocalSpotlights() { From 24e8a2bd6920cae69a957c807c60648bd9c7ea86 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 22:59:01 +0300 Subject: [PATCH 08/52] Make SpotlightSelector.ShowInfo use the full rankings response --- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 11 ++++++----- osu.Game/Overlays/RankingsOverlay.cs | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 8af6c0bb3b..8bf75b2357 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -14,6 +14,7 @@ using osuTK; using System; using System.Collections.Generic; using osu.Framework.Graphics.UserInterface; +using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.Rankings { @@ -101,12 +102,12 @@ namespace osu.Game.Overlays.Rankings background.Colour = colourProvider.Dark3; } - public void ShowInfo(APISpotlight spotlight, int mapCount) + public void ShowInfo(GetSpotlightRankingsResponse response) { - startDateColumn.Value = dateToString(spotlight.StartDate); - endDateColumn.Value = dateToString(spotlight.EndDate); - mapCountColumn.Value = mapCount.ToString(); - participantsColumn.Value = spotlight.Participants?.ToString("N0"); + startDateColumn.Value = dateToString(response.Spotlight.StartDate); + endDateColumn.Value = dateToString(response.Spotlight.EndDate); + mapCountColumn.Value = response.BeatmapSets.Count.ToString(); + participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); } protected override void PopIn() diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index ba0cc29d6d..99e81eef15 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -217,7 +217,7 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Result.Countries); case GetSpotlightRankingsRequest spotlightRequest: - header.SpotlightSelector.ShowInfo(spotlightRequest.Result.Spotlight, spotlightRequest.Result.BeatmapSets.Count); + header.SpotlightSelector.ShowInfo(spotlightRequest.Result); return new FillFlowContainer { AutoSizeAxes = Axes.Y, From 4dd25b42aeee0fb158dcdb05ab128733332842f5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 23:00:42 +0300 Subject: [PATCH 09/52] Move spotlightsRequest to another place --- osu.Game/Overlays/RankingsOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 99e81eef15..084c8b6e5a 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -36,7 +36,6 @@ namespace osu.Game.Overlays private APIRequest lastRequest; private CancellationTokenSource cancellationToken; - private GetSpotlightsRequest spotlightsRequest; [Resolved] private IAPIProvider api { get; set; } @@ -148,6 +147,8 @@ namespace osu.Game.Overlays Country.Value = requested; } + private GetSpotlightsRequest spotlightsRequest; + private void getSpotlights() { loading.Show(); From 7757a3a30bb7cfe33c0def5fe4489cddf7949796 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 23:06:13 +0300 Subject: [PATCH 10/52] Move spotlight layout to it's own method --- osu.Game/Overlays/RankingsOverlay.cs | 52 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 084c8b6e5a..4fc94420a4 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -218,34 +218,40 @@ namespace osu.Game.Overlays return new CountriesTable(1, countryRequest.Result.Countries); case GetSpotlightRankingsRequest spotlightRequest: - header.SpotlightSelector.ShowInfo(spotlightRequest.Result); - return new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new ScoresTable(1, spotlightRequest.Result.Users), - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(10), - Children = spotlightRequest.Result.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }).ToList() - } - } - }; + return getSpotlightContent(spotlightRequest.Result); } return null; } + private Drawable getSpotlightContent(GetSpotlightRankingsResponse response) + { + header.SpotlightSelector.ShowInfo(response); + + return new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new ScoresTable(1, response.Users), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(10), + Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }).ToList() + } + } + }; + } + private void loadContent(Drawable content) { scrollFlow.ScrollToStart(); From 881d192af3ecdc9cc9f950bd4e607b70bb3c969d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 7 Feb 2020 12:07:16 +0300 Subject: [PATCH 11/52] Fix crash when selecting spotlight tab with not-null country --- osu.Game/Overlays/RankingsOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 4fc94420a4..a0aeff082e 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -110,7 +110,8 @@ namespace osu.Game.Overlays if (Country.Value != null) Scope.Value = RankingsScope.Performance; - Scheduler.AddOnce(loadNewContent); + if (Scope.Value != RankingsScope.Spotlights) + Scheduler.AddOnce(loadNewContent); }, true); Scope.BindValueChanged(_ => From 88a56d00bfde67ba143e7b1d87a90d5b4ba0b5c4 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Feb 2020 20:11:37 -0800 Subject: [PATCH 12/52] Allow specifying order to SettingSource --- .../Mods/CatchModDifficultyAdjust.cs | 4 +-- .../Mods/OsuModDifficultyAdjust.cs | 4 +-- .../Configuration/SettingSourceAttribute.cs | 28 +++++++++++++++++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 +-- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 8377b3786a..802802c155 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Fruit Size", "Override a beatmap's set CS.")] + [SettingSource("Fruit Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.")] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", SettingSourceAttribute.LAST)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 7eee71be81..5326e36282 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.")] + [SettingSource("Circle Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.")] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", SettingSourceAttribute.LAST)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index a3788e4582..79232b70dc 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using osu.Framework.Bindables; @@ -20,14 +21,25 @@ namespace osu.Game.Configuration [AttributeUsage(AttributeTargets.Property)] public class SettingSourceAttribute : Attribute { + public const int FIRST = 0; + public const int ORDERED_RELATIVE = 1; + public const int UNORDERED = 2; + public const int LAST = 3; + public string Label { get; } public string Description { get; } - public SettingSourceAttribute(string label, string description = null) + public int OrderMode { get; } + + public int OrderPosition { get; } + + public SettingSourceAttribute(string label, string description = null, int order = UNORDERED, int orderPosition = 0) { Label = label ?? string.Empty; Description = description ?? string.Empty; + OrderMode = order; + OrderPosition = orderPosition; } } @@ -35,7 +47,7 @@ namespace osu.Game.Configuration { public static IEnumerable CreateSettingsControls(this object obj) { - foreach (var (attr, property) in obj.GetSettingsSourceProperties()) + foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties()) { object value = property.GetValue(obj); @@ -116,5 +128,17 @@ namespace osu.Game.Configuration yield return (attr, property); } } + + public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj) + { + var original = obj.GetSettingsSourceProperties(); + + var first = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.FIRST); + var orderedRelative = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.ORDERED_RELATIVE).OrderBy(attr => attr.Item1.OrderPosition); + var unordered = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.UNORDERED); + var last = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.LAST); + + return first.Concat(orderedRelative).Concat(unordered).Concat(last); + } } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index d74e2ce2bc..8c6b9658b0 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; - [SettingSource("Drain Rate", "Override a beatmap's set HP.")] + [SettingSource("Drain Rate", "Override a beatmap's set HP.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Overall Difficulty", "Override a beatmap's set OD.")] + [SettingSource("Overall Difficulty", "Override a beatmap's set OD.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, From 137181017b96ae3c7f786295c42023da197691ed Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Feb 2020 20:36:54 -0800 Subject: [PATCH 13/52] Naming consistency with osu!web --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 802802c155..f4754696ff 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Fruit Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 8c6b9658b0..fae04dd13e 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; - [SettingSource("Drain Rate", "Override a beatmap's set HP.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Overall Difficulty", "Override a beatmap's set OD.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, From ea521b466fdaa28dda69d8388f3f19a6183f040b Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Sun, 9 Feb 2020 21:37:40 -0800 Subject: [PATCH 14/52] Switch numerical consts to an enum --- .../Mods/CatchModDifficultyAdjust.cs | 5 ++-- .../Mods/OsuModDifficultyAdjust.cs | 5 ++-- .../Configuration/SettingSourceAttribute.cs | 25 +++++++++++-------- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 5 ++-- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index f4754696ff..17a2847fd1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -5,12 +5,13 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", OrderMode.FIRST)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", SettingSourceAttribute.LAST)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", OrderMode.LAST)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 5326e36282..2b56042ead 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -5,12 +5,13 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; +using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", SettingSourceAttribute.FIRST)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", OrderMode.FIRST)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", SettingSourceAttribute.LAST)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", OrderMode.LAST)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 79232b70dc..06e01bb042 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -21,24 +21,27 @@ namespace osu.Game.Configuration [AttributeUsage(AttributeTargets.Property)] public class SettingSourceAttribute : Attribute { - public const int FIRST = 0; - public const int ORDERED_RELATIVE = 1; - public const int UNORDERED = 2; - public const int LAST = 3; + public enum OrderMode + { + FIRST, + ORDERED_RELATIVE, + UNORDERED, + LAST + } public string Label { get; } public string Description { get; } - public int OrderMode { get; } + public OrderMode OrderingMode { get; } public int OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null, int order = UNORDERED, int orderPosition = 0) + public SettingSourceAttribute(string label, string description = null, OrderMode order = OrderMode.UNORDERED, int orderPosition = 0) { Label = label ?? string.Empty; Description = description ?? string.Empty; - OrderMode = order; + OrderingMode = order; OrderPosition = orderPosition; } } @@ -133,10 +136,10 @@ namespace osu.Game.Configuration { var original = obj.GetSettingsSourceProperties(); - var first = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.FIRST); - var orderedRelative = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.ORDERED_RELATIVE).OrderBy(attr => attr.Item1.OrderPosition); - var unordered = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.UNORDERED); - var last = original.Where(attr => attr.Item1.OrderMode == SettingSourceAttribute.LAST); + var first = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.FIRST); + var orderedRelative = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.ORDERED_RELATIVE).OrderBy(attr => attr.Item1.OrderPosition); + var unordered = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.UNORDERED); + var last = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.LAST); return first.Concat(orderedRelative).Concat(unordered).Concat(last); } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index fae04dd13e..958a0353ea 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; using osu.Game.Configuration; +using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Mods { @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; - [SettingSource("HP Drain", "Override a beatmap's set HP.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", OrderMode.ORDERED_RELATIVE, 1)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", SettingSourceAttribute.ORDERED_RELATIVE, 1)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", OrderMode.ORDERED_RELATIVE, 1)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, From a988a53d69fc23585f1aaa08b79b03bb2c31f708 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 Feb 2020 16:41:10 +0900 Subject: [PATCH 15/52] Better handle beatmap task cancel exception --- osu.Game/Beatmaps/WorkingBeatmap.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5dc483b61c..1adf502004 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -159,8 +159,16 @@ namespace osu.Game.Beatmaps { return LoadBeatmapAsync().Result; } - catch (TaskCanceledException) + catch (AggregateException ae) { + foreach (var e in ae.InnerExceptions) + { + if (e is TaskCanceledException) + continue; + + Logger.Log(e.ToString()); + } + return null; } } From 51e2a934bdbfa6045ec94ee04f6e629521e8cb9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 Feb 2020 17:00:51 +0900 Subject: [PATCH 16/52] Fix possible beatmap nullref in logo visualisation --- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 06ca161fed..dcc68296f6 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Menu private void updateAmplitudes() { var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; - var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; + var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; From cef682aa03b607079417a9eca096890689c9ec5a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 Feb 2020 17:01:41 +0900 Subject: [PATCH 17/52] Make WorkingBeatmap non-disposable --- osu.Game.Tests/WaveformTestBeatmap.cs | 4 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 67 ++++++++++++--------------- osu.Game/OsuGame.cs | 11 ++--- osu.Game/Tests/Visual/OsuTestScene.cs | 4 +- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index df6394ed34..53ce5def32 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -37,9 +37,9 @@ namespace osu.Game.Tests trackStore = audioManager.GetTrackStore(getZipReader()); } - protected override void Dispose(bool isDisposing) + ~WaveformTestBeatmap() { - base.Dispose(isDisposing); + // Remove the track store from the audio manager trackStore?.Dispose(); } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1adf502004..6478b33e27 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -17,10 +17,11 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osu.Framework.Graphics.Video; +using osu.Framework.Logging; namespace osu.Game.Beatmaps { - public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable + public abstract class WorkingBeatmap : IWorkingBeatmap { public readonly BeatmapInfo BeatmapInfo; @@ -133,11 +134,29 @@ namespace osu.Game.Beatmaps return converted; } - public override string ToString() => BeatmapInfo.ToString(); + private CancellationTokenSource loadCancellation = new CancellationTokenSource(); - public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; + /// + /// Beings loading the contents of this asynchronously. + /// + public void BeginAsyncLoad() + { + loadBeatmapAsync(); + } - public Task LoadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() => + /// + /// Cancels the asynchronous loading of the contents of this . + /// + public void CancelAsyncLoad() + { + loadCancellation?.Cancel(); + loadCancellation = new CancellationTokenSource(); + + if (beatmapLoadTask?.IsCompleted != true) + beatmapLoadTask = null; + } + + private Task loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() => { // Todo: Handle cancellation during beatmap parsing var b = GetBeatmap() ?? new Beatmap(); @@ -149,7 +168,11 @@ namespace osu.Game.Beatmaps b.BeatmapInfo = BeatmapInfo; return b; - }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + }, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + public override string ToString() => BeatmapInfo.ToString(); + + public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; public IBeatmap Beatmap { @@ -157,7 +180,7 @@ namespace osu.Game.Beatmaps { try { - return LoadBeatmapAsync().Result; + return loadBeatmapAsync().Result; } catch (AggregateException ae) { @@ -174,7 +197,6 @@ namespace osu.Game.Beatmaps } } - private readonly CancellationTokenSource beatmapCancellation = new CancellationTokenSource(); protected abstract IBeatmap GetBeatmap(); private Task beatmapLoadTask; @@ -225,40 +247,11 @@ namespace osu.Game.Beatmaps /// public virtual void RecycleTrack() => track.Recycle(); - #region Disposal - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - - isDisposed = true; - - // recycling logic is not here for the time being, as components which use - // retrieved objects from WorkingBeatmap may not hold a reference to the WorkingBeatmap itself. - // this should be fine as each retrieved component do have their own finalizers. - - // cancelling the beatmap load is safe for now since the retrieval is a synchronous - // operation. if we add an async retrieval method this may need to be reconsidered. - beatmapCancellation?.Cancel(); - total_count.Value--; - } - ~WorkingBeatmap() { - Dispose(false); + total_count.Value--; } - #endregion - public class RecyclableLazy { private Lazy lazy; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff3dee55af..f848b1a328 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -401,15 +401,14 @@ namespace osu.Game if (nextBeatmap?.Track != null) nextBeatmap.Track.Completed += currentTrackCompleted; - using (var oldBeatmap = beatmap.OldValue) - { - if (oldBeatmap?.Track != null) - oldBeatmap.Track.Completed -= currentTrackCompleted; - } + var oldBeatmap = beatmap.OldValue; + if (oldBeatmap?.Track != null) + oldBeatmap.Track.Completed -= currentTrackCompleted; updateModDefaults(); - nextBeatmap?.LoadBeatmapAsync(); + oldBeatmap?.CancelAsyncLoad(); + nextBeatmap?.BeginAsyncLoad(); } private void modsChanged(ValueChangedEvent> mods) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 41ab7fce99..b203557fab 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -191,9 +191,9 @@ namespace osu.Game.Tests.Visual track = audio?.Tracks.GetVirtual(length); } - protected override void Dispose(bool isDisposing) + ~ClockBackedTestWorkingBeatmap() { - base.Dispose(isDisposing); + // Remove the track store from the audio manager store?.Dispose(); } From 668f36d7f3286f18bcc8a5c1fd26dcfa198cd512 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 Feb 2020 17:04:31 +0900 Subject: [PATCH 18/52] Clean up logging --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 6478b33e27..36479ddbb7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps if (e is TaskCanceledException) continue; - Logger.Log(e.ToString()); + Logger.Log(e.Message); } return null; From 0ab3982494e59b4bad192c901073db53f4fd8034 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 10 Feb 2020 17:25:11 +0900 Subject: [PATCH 19/52] Unify error handling --- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 15 ++++++++++----- osu.Game/Beatmaps/WorkingBeatmap.cs | 16 +++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index f9d71a2a6e..55c5175c5d 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -36,8 +36,9 @@ namespace osu.Game.Beatmaps using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) return Decoder.GetDecoder(stream).Decode(stream); } - catch + catch (Exception e) { + Logger.Error(e, "Beatmap failed to load"); return null; } } @@ -59,8 +60,9 @@ namespace osu.Game.Beatmaps { return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); } - catch + catch (Exception e) { + Logger.Error(e, "Background failed to load"); return null; } } @@ -74,8 +76,9 @@ namespace osu.Game.Beatmaps { return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile))); } - catch + catch (Exception e) { + Logger.Error(e, "Video failed to load"); return null; } } @@ -86,8 +89,9 @@ namespace osu.Game.Beatmaps { return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile)); } - catch + catch (Exception e) { + Logger.Error(e, "Track failed to load"); return null; } } @@ -115,8 +119,9 @@ namespace osu.Game.Beatmaps var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); return trackData == null ? null : new Waveform(trackData); } - catch + catch (Exception e) { + Logger.Error(e, "Waveform failed to load"); return null; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 36479ddbb7..1e1ffad81e 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -184,14 +184,16 @@ namespace osu.Game.Beatmaps } catch (AggregateException ae) { - foreach (var e in ae.InnerExceptions) - { - if (e is TaskCanceledException) - continue; - - Logger.Log(e.Message); - } + // This is the exception that is generally expected here, which occurs via natural cancellation of the asynchronous load + if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException) + return null; + Logger.Error(ae, "Beatmap failed to load"); + return null; + } + catch (Exception e) + { + Logger.Error(e, "Beatmap failed to load"); return null; } } From b04a4b5c8ac28f3eb70150efee23f6085395854d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 Feb 2020 01:44:56 +0300 Subject: [PATCH 20/52] Implement SpotlightsLayout --- .../TestSceneRankingsSpotlightSelector.cs | 6 - .../Online/TestSceneSpotlightsLayout.cs | 55 ++++++ .../Rankings/RankingsOverlayHeader.cs | 6 +- .../Overlays/Rankings/SpotlightSelector.cs | 86 ++++------ .../Overlays/Rankings/SpotlightsLayout.cs | 158 ++++++++++++++++++ 5 files changed, 249 insertions(+), 62 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs create mode 100644 osu.Game/Overlays/Rankings/SpotlightsLayout.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs index f27ab1e775..e46c8a4a71 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs @@ -35,12 +35,6 @@ namespace osu.Game.Tests.Visual.Online Add(selector = new SpotlightSelector()); } - [Test] - public void TestVisibility() - { - AddStep("Toggle Visibility", selector.ToggleVisibility); - } - [Test] public void TestLocalSpotlights() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs b/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs new file mode 100644 index 0000000000..d025a8d7c2 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs @@ -0,0 +1,55 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Rankings; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneSpotlightsLayout : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SpotlightsLayout), + typeof(SpotlightSelector), + }; + + protected override bool UseOnlineAPI => true; + + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green); + + public TestSceneSpotlightsLayout() + { + var ruleset = new Bindable(new OsuRuleset().RulesetInfo); + + Add(new BasicScrollContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Child = new SpotlightsLayout + { + Ruleset = { BindTarget = ruleset } + } + }); + + AddStep("Osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("Mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddStep("Taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("Catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo); + } + } +} diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 538ae112b5..5b3744cf7f 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -60,8 +60,10 @@ namespace osu.Game.Overlays.Rankings base.LoadComplete(); } - private void onCurrentChanged(ValueChangedEvent scope) => - SpotlightSelector.State.Value = scope.NewValue == RankingsScope.Spotlights ? Visibility.Visible : Visibility.Hidden; + private void onCurrentChanged(ValueChangedEvent scope) + { + + } private class RankingsTitle : ScreenTitle { diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 8bf75b2357..bc2d731772 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -18,11 +18,8 @@ using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.Rankings { - public class SpotlightSelector : VisibilityContainer, IHasCurrentValue + public class SpotlightSelector : CompositeDrawable, IHasCurrentValue { - private const int height = 100; - private const int duration = 200; - private readonly Box background; private readonly SpotlightsDropdown dropdown; @@ -40,59 +37,52 @@ namespace osu.Game.Overlays.Rankings set => dropdown.Items = value; } - protected override bool StartHidden => true; - private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; private readonly InfoColumn mapCountColumn; private readonly InfoColumn participantsColumn; - private readonly Container content; public SpotlightSelector() { RelativeSizeAxes = Axes.X; - Add(content = new Container + Height = 100; + AddRangeInternal(new Drawable[] { - Height = height, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] + background = new Box { - background = new Box + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Children = new Drawable[] + dropdown = new SpotlightsDropdown { - dropdown = new SpotlightsDropdown + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Current = Current, + Depth = -float.MaxValue + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Current = Current, - Depth = -float.MaxValue - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new Drawable[] - { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") - } + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") } } - }, - } + } + }, }); } @@ -110,18 +100,6 @@ namespace osu.Game.Overlays.Rankings participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); } - protected override void PopIn() - { - this.ResizeHeightTo(height, duration, Easing.OutQuint); - content.FadeIn(duration, Easing.OutQuint); - } - - protected override void PopOut() - { - this.ResizeHeightTo(0, duration, Easing.OutQuint); - content.FadeOut(duration, Easing.OutQuint); - } - private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private class InfoColumn : FillFlowContainer diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs new file mode 100644 index 0000000000..fd1b73c4cd --- /dev/null +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -0,0 +1,158 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Game.Rulesets; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osuTK; +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Rankings.Tables; +using System.Linq; +using osu.Game.Overlays.Direct; +using System.Threading; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Rankings +{ + public class SpotlightsLayout : CompositeDrawable + { + public readonly Bindable Ruleset = new Bindable(); + + private readonly Bindable selectedSpotlight = new Bindable(); + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private CancellationTokenSource cancellationToken; + private GetSpotlightRankingsRequest getRankingsRequest; + private GetSpotlightsRequest spotlightsRequest; + + private readonly SpotlightSelector selector; + private readonly Container content; + private readonly DimmedLoadingLayer loading; + + public SpotlightsLayout() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + selector = new SpotlightSelector + { + Current = selectedSpotlight, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + loading = new DimmedLoadingLayer(), + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedSpotlight.BindValueChanged(onSpotlightChanged); + Ruleset.BindValueChanged(onRulesetChanged); + + getSpotlights(); + } + + private void getSpotlights() + { + spotlightsRequest = new GetSpotlightsRequest(); + spotlightsRequest.Success += response => selector.Spotlights = response.Spotlights; + api.Queue(spotlightsRequest); + } + + private void onRulesetChanged(ValueChangedEvent ruleset) + { + if (!selector.Spotlights.Any()) + return; + + selectedSpotlight.TriggerChange(); + } + + private void onSpotlightChanged(ValueChangedEvent spotlight) + { + loading.Show(); + + cancellationToken?.Cancel(); + getRankingsRequest?.Cancel(); + + getRankingsRequest = new GetSpotlightRankingsRequest(Ruleset.Value, spotlight.NewValue.Id); + getRankingsRequest.Success += onSuccess; + api.Queue(getRankingsRequest); + } + + private void onSuccess(GetSpotlightRankingsResponse response) + { + LoadComponentAsync(createContent(response), loaded => + { + selector.ShowInfo(response); + + content.Clear(); + content.Add(loaded); + + loading.Hide(); + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + private Drawable createContent(GetSpotlightRankingsResponse response) => new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new ScoresTable(1, response.Users), + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(10), + Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }).ToList() + } + } + }; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + spotlightsRequest?.Cancel(); + getRankingsRequest?.Cancel(); + cancellationToken?.Cancel(); + } + } +} From 0b6558dc40e9813712c0e6c0bfc28e354d62da42 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 11 Feb 2020 02:35:23 +0300 Subject: [PATCH 21/52] Add SpotlightsLayout to RankingsOverlay --- .../Visual/Online/TestSceneRankingsHeader.cs | 21 +--- .../TestSceneRankingsSpotlightSelector.cs | 6 ++ .../Rankings/RankingsOverlayHeader.cs | 40 +------- .../Overlays/Rankings/SpotlightSelector.cs | 73 ++++++++------ .../Overlays/Rankings/SpotlightsLayout.cs | 15 +-- osu.Game/Overlays/RankingsOverlay.cs | 97 +++++-------------- 6 files changed, 85 insertions(+), 167 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index bc0ae3d264..1e711b3cd7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Rankings; using osu.Game.Rulesets; @@ -35,25 +34,7 @@ namespace osu.Game.Tests.Visual.Online { Current = { BindTarget = scope }, Country = { BindTarget = countryBindable }, - Ruleset = { BindTarget = ruleset }, - Spotlights = new[] - { - new APISpotlight - { - Id = 1, - Name = "Spotlight 1" - }, - new APISpotlight - { - Id = 2, - Name = "Spotlight 2" - }, - new APISpotlight - { - Id = 3, - Name = "Spotlight 3" - } - } + Ruleset = { BindTarget = ruleset } }); var country = new Country diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs index e46c8a4a71..f27ab1e775 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs @@ -35,6 +35,12 @@ namespace osu.Game.Tests.Visual.Online Add(selector = new SpotlightSelector()); } + [Test] + public void TestVisibility() + { + AddStep("Toggle Visibility", selector.ToggleVisibility); + } + [Test] public void TestLocalSpotlights() { diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 5b3744cf7f..2674b3a81e 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -6,24 +6,14 @@ using osu.Framework.Bindables; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Rankings { public class RankingsOverlayHeader : TabControlOverlayHeader { public readonly Bindable Ruleset = new Bindable(); - public readonly Bindable Spotlight = new Bindable(); public readonly Bindable Country = new Bindable(); - public IEnumerable Spotlights - { - get => SpotlightSelector.Spotlights; - set => SpotlightSelector.Spotlights = value; - } - protected override ScreenTitle CreateTitle() => new RankingsTitle { Scope = { BindTarget = Current } @@ -34,37 +24,11 @@ namespace osu.Game.Overlays.Rankings Current = Ruleset }; - public SpotlightSelector SpotlightSelector; - - protected override Drawable CreateContent() => new FillFlowContainer + protected override Drawable CreateContent() => new CountryFilter { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new CountryFilter - { - Current = Country - }, - SpotlightSelector = new SpotlightSelector - { - Current = { BindTarget = Spotlight } - } - } + Current = Country }; - protected override void LoadComplete() - { - Current.BindValueChanged(onCurrentChanged, true); - base.LoadComplete(); - } - - private void onCurrentChanged(ValueChangedEvent scope) - { - - } - private class RankingsTitle : ScreenTitle { public readonly Bindable Scope = new Bindable(); diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index bc2d731772..f019b50ae8 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -18,8 +18,10 @@ using osu.Game.Online.API.Requests; namespace osu.Game.Overlays.Rankings { - public class SpotlightSelector : CompositeDrawable, IHasCurrentValue + public class SpotlightSelector : VisibilityContainer, IHasCurrentValue { + private const int duration = 300; + private readonly Box background; private readonly SpotlightsDropdown dropdown; @@ -37,52 +39,59 @@ namespace osu.Game.Overlays.Rankings set => dropdown.Items = value; } + protected override bool StartHidden => true; + private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; private readonly InfoColumn mapCountColumn; private readonly InfoColumn participantsColumn; + private readonly Container content; public SpotlightSelector() { RelativeSizeAxes = Axes.X; Height = 100; - AddRangeInternal(new Drawable[] + Add(content = new Container { - background = new Box + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Children = new Drawable[] + background = new Box { - dropdown = new SpotlightsDropdown + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Current = Current, - Depth = -float.MaxValue - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new Drawable[] + dropdown = new SpotlightsDropdown { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Current = Current, + Depth = -float.MaxValue + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Children = new Drawable[] + { + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") + } } } } - }, + } }); } @@ -100,6 +109,10 @@ namespace osu.Game.Overlays.Rankings participantsColumn.Value = response.Spotlight.Participants?.ToString("N0"); } + protected override void PopIn() => content.FadeIn(duration, Easing.OutQuint); + + protected override void PopOut() => content.FadeOut(duration, Easing.OutQuint); + private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private class InfoColumn : FillFlowContainer diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index fd1b73c4cd..33811cc982 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -35,11 +35,12 @@ namespace osu.Game.Overlays.Rankings private GetSpotlightRankingsRequest getRankingsRequest; private GetSpotlightsRequest spotlightsRequest; - private readonly SpotlightSelector selector; - private readonly Container content; - private readonly DimmedLoadingLayer loading; + private SpotlightSelector selector; + private Container content; + private DimmedLoadingLayer loading; - public SpotlightsLayout() + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -48,7 +49,6 @@ namespace osu.Game.Overlays.Rankings RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), Children = new Drawable[] { selector = new SpotlightSelector @@ -65,8 +65,9 @@ namespace osu.Game.Overlays.Rankings { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Vertical = 10 } }, - loading = new DimmedLoadingLayer(), + loading = new DimmedLoadingLayer() } } } @@ -77,6 +78,8 @@ namespace osu.Game.Overlays.Rankings { base.LoadComplete(); + selector.Show(); + selectedSpotlight.BindValueChanged(onSpotlightChanged); Ruleset.BindValueChanged(onRulesetChanged); diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index a0aeff082e..f3215d07fa 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -14,10 +14,6 @@ using osu.Game.Online.API; using System.Threading; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Rankings.Tables; -using osu.Game.Online.API.Requests.Responses; -using System.Linq; -using osuTK; -using osu.Game.Overlays.Direct; namespace osu.Game.Overlays { @@ -26,13 +22,11 @@ namespace osu.Game.Overlays protected readonly Bindable Country = new Bindable(); protected readonly Bindable Scope = new Bindable(); private readonly Bindable ruleset = new Bindable(); - private readonly Bindable spotlight = new Bindable(); private readonly BasicScrollContainer scrollFlow; private readonly Container contentContainer; private readonly DimmedLoadingLayer loading; private readonly Box background; - private readonly RankingsOverlayHeader header; private APIRequest lastRequest; private CancellationTokenSource cancellationToken; @@ -40,9 +34,6 @@ namespace osu.Game.Overlays [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - public RankingsOverlay() : base(OverlayColourScheme.Green) { @@ -63,15 +54,14 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new RankingsOverlayHeader + new RankingsOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Depth = -float.MaxValue, Country = { BindTarget = Country }, Current = { BindTarget = Scope }, - Ruleset = { BindTarget = ruleset }, - Spotlight = { BindTarget = spotlight } + Ruleset = { BindTarget = ruleset } }, new Container { @@ -85,7 +75,7 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Vertical = 10 } + Margin = new MarginPadding { Bottom = 10 } }, loading = new DimmedLoadingLayer(), } @@ -110,30 +100,25 @@ namespace osu.Game.Overlays if (Country.Value != null) Scope.Value = RankingsScope.Performance; - if (Scope.Value != RankingsScope.Spotlights) - Scheduler.AddOnce(loadNewContent); + Scheduler.AddOnce(loadNewContent); }, true); Scope.BindValueChanged(_ => { - spotlightsRequest?.Cancel(); - // country filtering is only valid for performance scope. if (Scope.Value != RankingsScope.Performance) Country.Value = null; - if (Scope.Value == RankingsScope.Spotlights && !header.Spotlights.Any()) - { - getSpotlights(); - return; - } - Scheduler.AddOnce(loadNewContent); }, true); - ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + ruleset.BindValueChanged(_ => + { + if (Scope.Value == RankingsScope.Spotlights) + return; - spotlight.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); + Scheduler.AddOnce(loadNewContent); + }, true); base.LoadComplete(); } @@ -148,16 +133,6 @@ namespace osu.Game.Overlays Country.Value = requested; } - private GetSpotlightsRequest spotlightsRequest; - - private void getSpotlights() - { - loading.Show(); - spotlightsRequest = new GetSpotlightsRequest(); - spotlightsRequest.Success += response => header.Spotlights = response.Spotlights; - api.Queue(spotlightsRequest); - } - private void loadNewContent() { loading.Show(); @@ -165,6 +140,15 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); lastRequest?.Cancel(); + if (Scope.Value == RankingsScope.Spotlights) + { + loadContent(new SpotlightsLayout + { + Ruleset = { BindTarget = ruleset } + }); + return; + } + var request = createScopedRequest(); lastRequest = request; @@ -174,8 +158,9 @@ namespace osu.Game.Overlays return; } - request.Success += () => loadContent(createContentFromResponse(request)); + request.Success += () => loadContent(createTableFromResponse(request)); request.Failure += _ => loadContent(null); + api.Queue(request); } @@ -191,15 +176,12 @@ namespace osu.Game.Overlays case RankingsScope.Score: return new GetUserRankingsRequest(ruleset.Value, UserRankingsType.Score); - - case RankingsScope.Spotlights: - return new GetSpotlightRankingsRequest(ruleset.Value, header.Spotlight.Value.Id); } return null; } - private Drawable createContentFromResponse(APIRequest request) + private Drawable createTableFromResponse(APIRequest request) { switch (request) { @@ -217,42 +199,11 @@ namespace osu.Game.Overlays case GetCountryRankingsRequest countryRequest: return new CountriesTable(1, countryRequest.Result.Countries); - - case GetSpotlightRankingsRequest spotlightRequest: - return getSpotlightContent(spotlightRequest.Result); } return null; } - private Drawable getSpotlightContent(GetSpotlightRankingsResponse response) - { - header.SpotlightSelector.ShowInfo(response); - - return new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Children = new Drawable[] - { - new ScoresTable(1, response.Users), - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }).ToList() - } - } - }; - } - private void loadContent(Drawable content) { scrollFlow.ScrollToStart(); @@ -264,10 +215,10 @@ namespace osu.Game.Overlays return; } - LoadComponentAsync(content, t => + LoadComponentAsync(content, loaded => { loading.Hide(); - contentContainer.Child = content; + contentContainer.Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } } From ca237fd987cc677f7bbda1923a421836763f884d Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Mon, 10 Feb 2020 16:21:49 -0800 Subject: [PATCH 22/52] Simplify ordering by using only numbers, add xmldoc --- .../Mods/CatchModDifficultyAdjust.cs | 5 ++-- .../Mods/OsuModDifficultyAdjust.cs | 5 ++-- .../Configuration/SettingSourceAttribute.cs | 25 ++++++------------- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 5 ++-- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 17a2847fd1..de7adaa261 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -5,13 +5,12 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; -using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", OrderMode.FIRST)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", 0)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", OrderMode.LAST)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", 3)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 2b56042ead..2c6eb19f3d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -5,13 +5,12 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; -using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", OrderMode.FIRST)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", 0)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", OrderMode.LAST)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", 3)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 06e01bb042..4dcd2c6122 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -17,31 +17,24 @@ namespace osu.Game.Configuration /// An attribute to mark a bindable as being exposed to the user via settings controls. /// Can be used in conjunction with to automatically create UI controls. /// + /// + /// All controls with OrderPosition set to an int greater than 0 will be placed first in ascending order. + /// All controls with no OrderPosition will come afterward in default order. + /// [MeansImplicitUse] [AttributeUsage(AttributeTargets.Property)] public class SettingSourceAttribute : Attribute { - public enum OrderMode - { - FIRST, - ORDERED_RELATIVE, - UNORDERED, - LAST - } - public string Label { get; } public string Description { get; } - public OrderMode OrderingMode { get; } - public int OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null, OrderMode order = OrderMode.UNORDERED, int orderPosition = 0) + public SettingSourceAttribute(string label, string description = null, int orderPosition = -1) { Label = label ?? string.Empty; Description = description ?? string.Empty; - OrderingMode = order; OrderPosition = orderPosition; } } @@ -136,12 +129,10 @@ namespace osu.Game.Configuration { var original = obj.GetSettingsSourceProperties(); - var first = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.FIRST); - var orderedRelative = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.ORDERED_RELATIVE).OrderBy(attr => attr.Item1.OrderPosition); - var unordered = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.UNORDERED); - var last = original.Where(attr => attr.Item1.OrderingMode == SettingSourceAttribute.OrderMode.LAST); + var orderedRelative = original.Where(attr => attr.Item1.OrderPosition > -1).OrderBy(attr => attr.Item1.OrderPosition); + var unordered = original.Except(orderedRelative); - return first.Concat(orderedRelative).Concat(unordered).Concat(last); + return orderedRelative.Concat(unordered); } } } diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 958a0353ea..6ba54ff8fb 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Sprites; using System; using System.Collections.Generic; using osu.Game.Configuration; -using static osu.Game.Configuration.SettingSourceAttribute; namespace osu.Game.Rulesets.Mods { @@ -29,7 +28,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; - [SettingSource("HP Drain", "Override a beatmap's set HP.", OrderMode.ORDERED_RELATIVE, 1)] + [SettingSource("HP Drain", "Override a beatmap's set HP.", 1)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -39,7 +38,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", OrderMode.ORDERED_RELATIVE, 1)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", 2)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, From e9b51371476ca3adb6a00e9aca26ff5b600dae51 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Tue, 11 Feb 2020 21:52:22 -0800 Subject: [PATCH 23/52] Remove >-1 limitation by using a separate constructor --- osu.Game/Configuration/SettingSourceAttribute.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 4dcd2c6122..013621d7b7 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -29,12 +29,17 @@ namespace osu.Game.Configuration public string Description { get; } - public int OrderPosition { get; } + public int? OrderPosition { get; } - public SettingSourceAttribute(string label, string description = null, int orderPosition = -1) + public SettingSourceAttribute(string label, string description = null) { Label = label ?? string.Empty; Description = description ?? string.Empty; + } + + public SettingSourceAttribute(string label, string description, int orderPosition) + : this(label, description) + { OrderPosition = orderPosition; } } @@ -129,7 +134,7 @@ namespace osu.Game.Configuration { var original = obj.GetSettingsSourceProperties(); - var orderedRelative = original.Where(attr => attr.Item1.OrderPosition > -1).OrderBy(attr => attr.Item1.OrderPosition); + var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition); var unordered = original.Except(orderedRelative); return orderedRelative.Concat(unordered); From c386589cc09682ccf21db710775aa4bc6420e64e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2020 19:02:25 +0900 Subject: [PATCH 24/52] Reapply current state, not idle --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e391157b5b..f4ce71b803 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Objects.Drawables HitObject.DefaultsApplied += onDefaultsApplied; startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); - startTimeBindable.BindValueChanged(_ => updateState(ArmedState.Idle, true)); + startTimeBindable.BindValueChanged(_ => updateState(State.Value, true)); if (HitObject is IHasComboInformation combo) { From f7ee675102c4dc76f6b69711046f4678515988b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2020 19:02:54 +0900 Subject: [PATCH 25/52] Clear and revert to negative infinity, avoiding transforms getting left behind on StartTime change --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f4ce71b803..d3a0b3450f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -250,8 +250,8 @@ namespace osu.Game.Rulesets.Objects.Drawables double transformTime = HitObject.StartTime - InitialLifetimeOffset; - base.ApplyTransformsAt(transformTime, true); - base.ClearTransformsAfter(transformTime, true); + base.ApplyTransformsAt(double.MinValue, true); + base.ClearTransformsAfter(double.MinValue, true); using (BeginAbsoluteSequence(transformTime, true)) { From 85a443783755a52cc2d46581644180a264339a2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Feb 2020 19:03:22 +0900 Subject: [PATCH 26/52] Fix editor custom FadeOut causing overlapping issues by removing existing FadeOut --- osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index 22b4c3e82e..a8719e0aa8 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit if (existing == null) return; + hitObject.RemoveTransform(existing); + using (hitObject.BeginAbsoluteSequence(existing.StartTime)) hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire(); break; From a8eb9ba45c71fe67fce625c74776919dea430d82 Mon Sep 17 00:00:00 2001 From: voidedWarranties Date: Wed, 12 Feb 2020 15:55:16 -0800 Subject: [PATCH 27/52] Update xmldoc --- osu.Game/Configuration/SettingSourceAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 013621d7b7..4bdbb5fc24 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -18,8 +18,8 @@ namespace osu.Game.Configuration /// Can be used in conjunction with to automatically create UI controls. /// /// - /// All controls with OrderPosition set to an int greater than 0 will be placed first in ascending order. - /// All controls with no OrderPosition will come afterward in default order. + /// All controls with set will be placed first in ascending order. + /// All controls with no will come afterward in default order. /// [MeansImplicitUse] [AttributeUsage(AttributeTargets.Property)] From 0fe41fd50a04c6b481eb8df4ad4b4622afda7f32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 09:03:48 +0900 Subject: [PATCH 28/52] Fix blueprint showing even when mouse outside of container --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 2 +- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Spinners/SpinnerPlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 14 ++++++++------ .../Components/ComposeBlueprintContainer.cs | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index bcbc1ee527..7bbde400ea 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdatePosition(screenSpacePosition); - if (PlacementBegun) + if (PlacementActive) { var endTime = TimeAt(screenSpacePosition); diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 362d6d40a8..a3657d3bb9 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public override void UpdatePosition(Vector2 screenSpacePosition) { - if (!PlacementBegun) + if (!PlacementActive) Column = ColumnAt(screenSpacePosition); if (Column == null) return; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 75d05b9b6c..a780653796 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void beginCurve() { - BeginPlacement(); + BeginPlacement(commitStart: true); setState(PlacementState.Body); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 2c125aa7c3..74b563d922 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners if (e.Button != MouseButton.Left) return false; - BeginPlacement(); + BeginPlacement(commitStart: true); piece.FadeTo(1f, 150, Easing.OutQuint); isPlacingEnd = true; diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 24fa96e1c5..8a4ed61d00 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Edit public event Action StateChanged; /// - /// Whether the is currently being placed, but has not necessarily finished being placed. + /// Whether the is currently mid-placement, but has not necessarily finished being placed. /// - public bool PlacementBegun { get; private set; } + public bool PlacementActive { get; private set; } /// /// The that is being placed. @@ -92,23 +92,25 @@ namespace osu.Game.Rulesets.Edit /// Signals that the placement of has started. /// /// The start time of at the placement point. If null, the current clock time is used. - protected void BeginPlacement(double? startTime = null) + /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. + protected void BeginPlacement(double? startTime = null, bool commitStart = false) { HitObject.StartTime = startTime ?? EditorClock.CurrentTime; placementHandler.BeginPlacement(HitObject); - PlacementBegun = true; + PlacementActive |= commitStart; } /// /// Signals that the placement of has finished. - /// This will destroy this , and add the to the . + /// This will destroy this , and add the HitObject.StartTime to the . /// /// Whether the object should be committed. public void EndPlacement(bool commit) { - if (!PlacementBegun) + if (!PlacementActive) BeginPlacement(); placementHandler.EndPlacement(HitObject, commit); + PlacementActive = false; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index b257688568..9ebfaef563 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (composer.CursorInPlacementArea) currentPlacement.State = PlacementState.Shown; - else if (currentPlacement?.PlacementBegun == false) + else if (currentPlacement?.PlacementActive == false) currentPlacement.State = PlacementState.Hidden; } } From b65e839bd26c00507d2038cfca6cc85e2ef218be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 10:00:09 +0900 Subject: [PATCH 29/52] Simplify blueprints by removing visible state --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 38 +------------------ .../Components/ComposeBlueprintContainer.cs | 22 ++++++----- 2 files changed, 14 insertions(+), 46 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 8a4ed61d00..ea77a6091a 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -1,8 +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 osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,13 +18,8 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint which governs the creation of a new to actualisation. /// - public abstract class PlacementBlueprint : CompositeDrawable, IStateful + public abstract class PlacementBlueprint : CompositeDrawable { - /// - /// Invoked when has changed. - /// - public event Action StateChanged; - /// /// Whether the is currently mid-placement, but has not necessarily finished being placed. /// @@ -53,8 +46,6 @@ namespace osu.Game.Rulesets.Edit // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle // on the same frame it is made visible via a PlacementState change. AlwaysPresent = true; - - Alpha = 0; } [BackgroundDependencyLoader] @@ -67,27 +58,6 @@ namespace osu.Game.Rulesets.Edit ApplyDefaultsToHitObject(); } - private PlacementState state; - - public PlacementState State - { - get => state; - set - { - if (state == value) - return; - - state = value; - - if (state == PlacementState.Shown) - Show(); - else - Hide(); - - StateChanged?.Invoke(value); - } - } - /// /// Signals that the placement of has started. /// @@ -144,10 +114,4 @@ namespace osu.Game.Rulesets.Edit } } } - - public enum PlacementState - { - Hidden, - Shown, - } } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 9ebfaef563..48c570b8d0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -64,8 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { placementBlueprintContainer.Clear(); - currentPlacement?.EndPlacement(false); - currentPlacement = null; + removePlacement(); var blueprint = CurrentTool?.CreatePlacementBlueprint(); @@ -103,18 +102,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - if (currentPlacement != null) - { - if (composer.CursorInPlacementArea) - currentPlacement.State = PlacementState.Shown; - else if (currentPlacement?.PlacementActive == false) - currentPlacement.State = PlacementState.Hidden; - } + if (currentPlacement?.PlacementActive == false && !composer.CursorInPlacementArea) + removePlacement(); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) { var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject); + if (drawable == null) return null; @@ -129,6 +124,14 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(hitObject); } + private void removePlacement() + { + if (currentPlacement == null) return; + + currentPlacement.EndPlacement(false); + currentPlacement = null; + } + private HitObjectCompositionTool currentTool; /// @@ -137,6 +140,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public HitObjectCompositionTool CurrentTool { get => currentTool; + set { if (currentTool == value) From 2b6f99d4043adef41cd9740a72bd84db1d37b17f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 10:05:50 +0900 Subject: [PATCH 30/52] Standardise placement blueprint creation and destruction --- .../Components/ComposeBlueprintContainer.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 48c570b8d0..8b47ea2c6c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -62,19 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// private void refreshTool() { - placementBlueprintContainer.Clear(); - removePlacement(); - - var blueprint = CurrentTool?.CreatePlacementBlueprint(); - - if (blueprint != null) - { - placementBlueprintContainer.Child = currentPlacement = blueprint; - - // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - updatePlacementPosition(inputManager.CurrentState.Mouse.Position); - } + createPlacement(); } private void updatePlacementPosition(Vector2 screenSpacePosition) @@ -102,7 +91,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - if (currentPlacement?.PlacementActive == false && !composer.CursorInPlacementArea) + if (composer.CursorInPlacementArea) + createPlacement(); + else if (currentPlacement?.PlacementActive == false) removePlacement(); } @@ -124,11 +115,27 @@ namespace osu.Game.Screens.Edit.Compose.Components base.AddBlueprintFor(hitObject); } + private void createPlacement() + { + if (currentPlacement != null) return; + + var blueprint = CurrentTool?.CreatePlacementBlueprint(); + + if (blueprint != null) + { + placementBlueprintContainer.Child = currentPlacement = blueprint; + + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + updatePlacementPosition(inputManager.CurrentState.Mouse.Position); + } + } + private void removePlacement() { if (currentPlacement == null) return; currentPlacement.EndPlacement(false); + currentPlacement.Expire(); currentPlacement = null; } From e34a24a063dc7e67a48dc16abe1463012e4133ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 10:45:16 +0900 Subject: [PATCH 31/52] Update placement blueprint more often for better display --- .../Components/ComposeBlueprintContainer.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 8b47ea2c6c..7087eac6b6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -76,17 +76,6 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (currentPlacement != null) - { - updatePlacementPosition(e.ScreenSpaceMousePosition); - return true; - } - - return base.OnMouseMove(e); - } - protected override void Update() { base.Update(); @@ -95,6 +84,9 @@ namespace osu.Game.Screens.Edit.Compose.Components createPlacement(); else if (currentPlacement?.PlacementActive == false) removePlacement(); + + if (currentPlacement != null) + updatePlacementPosition(inputManager.CurrentState.Mouse.Position); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) From 487dd47c9e9496969921ed90986297c1f4318422 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 11:14:09 +0900 Subject: [PATCH 32/52] Add mouse down repeat support to timeline zoom buttons --- .../Components/Timeline/TimelineButton.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 8865bf31ea..5550c6a748 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -6,10 +6,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -52,6 +55,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline HoverColour = OsuColour.Gray(0.25f); FlashColour = OsuColour.Gray(0.5f); } + + private ScheduledDelegate repeatSchedule; + + /// + /// The initial delay before mouse down repeat begins. + /// + private const int repeat_initial_delay = 250; + + /// + /// The delay between mouse down repeats after the initial repeat. + /// + private const int repeat_tick_rate = 70; + + protected override bool OnClick(ClickEvent e) + { + // don't actuate a click since we are manually handling repeats. + return true; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + { + Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button)); + + // run once for initial down + clickAction(); + + Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate)); + } + + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + repeatSchedule?.Cancel(); + base.OnMouseUp(e); + } } } } From 03bf10f9a24a7a5b259e13435a9979c4e32adfea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 11:15:00 +0900 Subject: [PATCH 33/52] Remove unused using statement --- .../Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 7087eac6b6..0eb77a8561 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; From 045d1f9c5b8db2974b53f8157bad41e0d2947e9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 13:34:59 +0900 Subject: [PATCH 34/52] Disallow seeking on osu!direct download progress bars --- osu.Game/Overlays/Direct/DownloadProgressBar.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs index a6cefaae84..9a8644efd2 100644 --- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs +++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Direct public DownloadProgressBar(BeatmapSetInfo beatmapSet) : base(beatmapSet) { - AddInternal(progressBar = new ProgressBar + AddInternal(progressBar = new InteractionDisabledProgressBar { Height = 0, Alpha = 0, @@ -64,5 +64,11 @@ namespace osu.Game.Overlays.Direct } }, true); } + + private class InteractionDisabledProgressBar : ProgressBar + { + public override bool HandlePositionalInput => false; + public override bool HandleNonPositionalInput => false; + } } } From 53b62816f8db94b3aa90fe0be77ceca296a4feab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 14:07:37 +0900 Subject: [PATCH 35/52] Add index constants for cross-class safety --- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 8 ++++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index de7adaa261..e2465d727e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", 0)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", 3)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 2c6eb19f3d..75de6896a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDifficultyAdjust : ModDifficultyAdjust { - [SettingSource("Circle Size", "Override a beatmap's set CS.", 0)] + [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)] public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods Value = 5, }; - [SettingSource("Approach Rate", "Override a beatmap's set AR.", 3)] + [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)] public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index 6ba54ff8fb..2083671072 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -28,7 +28,11 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; - [SettingSource("HP Drain", "Override a beatmap's set HP.", 1)] + protected const int FIRST_SETTING_ORDER = 1; + + protected const int LAST_SETTING_ORDER = 2; + + [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)] public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, @@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Mods Value = 5, }; - [SettingSource("Accuracy", "Override a beatmap's set OD.", 2)] + [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)] public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, From d4f14e552a26c041cf09f61043e2d96ac2e86a1e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Feb 2020 18:05:53 +0900 Subject: [PATCH 36/52] Improve extensibility of mod display expansion --- .../UserInterface/TestSceneModDisplay.cs | 40 +++++++++++++++++++ osu.Game/Screens/Play/HUD/ModDisplay.cs | 28 +++++++++++-- osu.Game/Screens/Select/FooterButtonMods.cs | 2 +- 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs new file mode 100644 index 0000000000..8168faa106 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModDisplay : OsuTestScene + { + [TestCase(ExpansionMode.ExpandOnHover)] + [TestCase(ExpansionMode.AlwaysExpanded)] + [TestCase(ExpansionMode.AlwaysContracted)] + public void TestMode(ExpansionMode mode) + { + AddStep("create mod display", () => + { + Child = new ModDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ExpansionMode = mode, + Current = + { + Value = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModDifficultyAdjust(), + new OsuModEasy(), + } + } + }; + }); + } + } +} diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 00edd4db99..336b03544f 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD public bool DisplayUnrankedText = true; - public bool AllowExpand = true; + public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; private readonly Bindable> current = new Bindable>(); @@ -110,11 +110,15 @@ namespace osu.Game.Screens.Play.HUD private void expand() { - if (AllowExpand) + if (ExpansionMode != ExpansionMode.AlwaysContracted) IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); } - private void contract() => IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + private void contract() + { + if (ExpansionMode != ExpansionMode.AlwaysExpanded) + IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + } protected override bool OnHover(HoverEvent e) { @@ -128,4 +132,22 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } } + + public enum ExpansionMode + { + /// + /// The will expand only when hovered. + /// + ExpandOnHover, + + /// + /// The will always be expanded. + /// + AlwaysExpanded, + + /// + /// The will always be contracted. + /// + AlwaysContracted + } } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 4f2369847f..2411cf26f9 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select public FooterModDisplay() { - AllowExpand = false; + ExpansionMode = ExpansionMode.AlwaysContracted; IconsContainer.Margin = new MarginPadding(); } } From 91edadfe9d918f191baeb56c5ae7b555dada624a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Feb 2020 18:12:47 +0900 Subject: [PATCH 37/52] Make playlist beatmap and ruleset into bindables --- .../TestSceneLoungeRoomsContainer.cs | 9 +++-- .../Multiplayer/TestSceneMatchBeatmapPanel.cs | 10 ++--- .../Multiplayer/TestSceneMatchHeader.cs | 19 ++++++---- .../Visual/Multiplayer/TestSceneMatchInfo.cs | 38 +++++++++++-------- .../TestSceneMatchSettingsOverlay.cs | 2 +- osu.Game/Online/Multiplayer/PlaylistItem.cs | 30 +++++++-------- .../Screens/Multi/Components/BeatmapTitle.cs | 6 +-- .../Multi/Components/BeatmapTypeInfo.cs | 2 +- .../Screens/Multi/Components/ModeTypeInfo.cs | 2 +- .../Components/MultiplayerBackgroundSprite.cs | 2 +- .../Multi/Lounge/Components/RoomsContainer.cs | 2 +- .../Screens/Multi/Match/Components/Header.cs | 1 + .../Screens/Multi/Match/Components/Info.cs | 2 +- .../Match/Components/MatchBeatmapPanel.cs | 2 +- .../Screens/Multi/Match/MatchSubScreen.cs | 7 ++-- .../Screens/Multi/Play/TimeshiftPlayer.cs | 4 +- osu.Game/Screens/Select/MatchSongSelect.cs | 9 +++-- 17 files changed, 79 insertions(+), 68 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index fe14a1ff0a..b5d946d049 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -121,10 +121,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { room.Playlist.Add(new PlaylistItem { - Ruleset = ruleset, - Beatmap = new BeatmapInfo + Ruleset = { Value = ruleset }, + Beatmap = { - Metadata = new BeatmapMetadata() + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } } }); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs index 1e3e06ce7a..84ab6f9ccc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -32,11 +32,11 @@ namespace osu.Game.Tests.Visual.Multiplayer Origin = Anchor.Centre, }); - Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } }); - Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } }); - Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } }); - Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } }); - Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1763072 } } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2101557 } } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1973466 } } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2109801 } } }); + Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1922035 } } }); } protected override void LoadComplete() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index e42042f2ea..7d7e7f85db 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -23,16 +23,19 @@ namespace osu.Game.Tests.Visual.Multiplayer { Room.Playlist.Add(new PlaylistItem { - Beatmap = new BeatmapInfo + Beatmap = { - Metadata = new BeatmapMetadata + Value = new BeatmapInfo { - Title = "Title", - Artist = "Artist", - AuthorString = "Author", - }, - Version = "Version", - Ruleset = new OsuRuleset().RulesetInfo + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + } }, RequiredMods = { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs index a6c036a876..6ee9ceb2dd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs @@ -37,16 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer Room.Playlist.Clear(); Room.Playlist.Add(new PlaylistItem { - Beatmap = new BeatmapInfo + Beatmap = { - StarDifficulty = 2.4, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata + Value = new BeatmapInfo { - Title = @"My Song", - Artist = @"VisualTests", - AuthorString = @"osu!lazer", - }, + StarDifficulty = 2.4, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"My Song", + Artist = @"VisualTests", + AuthorString = @"osu!lazer", + }, + } } }); }); @@ -60,16 +63,19 @@ namespace osu.Game.Tests.Visual.Multiplayer Room.Playlist.Clear(); Room.Playlist.Add(new PlaylistItem { - Beatmap = new BeatmapInfo + Beatmap = { - StarDifficulty = 4.2, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata + Value = new BeatmapInfo { - Title = @"Your Song", - Artist = @"Tester", - AuthorString = @"Someone", - }, + StarDifficulty = 4.2, + Ruleset = rulesets.GetRuleset(3), + Metadata = new BeatmapMetadata + { + Title = @"Your Song", + Artist = @"Tester", + AuthorString = @"Someone", + }, + } } }); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index 8d842fc865..047e9d860d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("set name", () => Room.Name.Value = "Room name"); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); - AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo })); + AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } })); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); AddStep("clear name", () => Room.Name.Value = ""); diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index 5f8edc607b..e243cfca08 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -1,9 +1,9 @@ // 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 Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -24,24 +24,16 @@ namespace osu.Game.Online.Multiplayer public int RulesetID { get; set; } [JsonIgnore] - public BeatmapInfo Beatmap - { - get => beatmap; - set - { - beatmap = value; - BeatmapID = value?.OnlineBeatmapID ?? 0; - } - } + public readonly Bindable Beatmap = new Bindable(); [JsonIgnore] - public RulesetInfo Ruleset { get; set; } + public readonly Bindable Ruleset = new Bindable(); [JsonIgnore] - public readonly List AllowedMods = new List(); + public readonly BindableList AllowedMods = new BindableList(); [JsonIgnore] - public readonly List RequiredMods = new List(); + public readonly BindableList RequiredMods = new BindableList(); [JsonProperty("beatmap")] private APIBeatmap apiBeatmap { get; set; } @@ -64,16 +56,20 @@ namespace osu.Game.Online.Multiplayer set => requiredModsBacking = value; } - private BeatmapInfo beatmap; + public PlaylistItem() + { + Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0); + Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0); + } public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) { // If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead // Todo: Is this a bug? Room creation only returns the beatmap ID - Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); - Ruleset = rulesets.GetRuleset(RulesetID); + Beatmap.Value = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); + Ruleset.Value = rulesets.GetRuleset(RulesetID); - Ruleset rulesetInstance = Ruleset.CreateInstance(); + Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); if (allowedModsBacking != null) { diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index b41b2d073e..b991a3d68d 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Multi.Components { new OsuSpriteText { - Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Text = new LocalisedString((beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist)), Font = OsuFont.GetFont(size: TextSize), }, new OsuSpriteText @@ -80,10 +80,10 @@ namespace osu.Game.Screens.Multi.Components }, new OsuSpriteText { - Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), + Text = new LocalisedString((beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title)), Font = OsuFont.GetFont(size: TextSize), } - }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); + }, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap"); } } } diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index d63f2fecd2..21b228bb5c 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Multi.Components if (beatmap != null) { beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); - beatmapAuthor.AddUserLink(beatmap.Metadata.Author); + beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author); } }, true); } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 6080458aec..5465463888 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Components if (item?.Beatmap != null) { drawableRuleset.FadeIn(transition_duration); - drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value) { Size = new Vector2(height) }; } else drawableRuleset.FadeOut(transition_duration); diff --git a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs index 968fa6e72e..9a1a482699 100644 --- a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs +++ b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Multi.Components InternalChild = sprite = CreateBackgroundSprite(); - CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true); + CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true); } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 063957d816..64618a1d85 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset)); if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index a52d43acf4..991060cac0 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index 74f000c21f..f0b54baa4c 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap, true); + CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap.Value, true); hostInfo.Host.BindTo(Host); } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index 7c1fe91393..f67f6d9f43 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Match.Components [BackgroundDependencyLoader] private void load() { - CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap), true); + CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap.Value), true); } private void loadNewPanel(BeatmapInfo beatmap) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index c2bb7da6b5..9165ef6d0f 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -183,13 +184,13 @@ namespace osu.Game.Screens.Multi.Match private void currentItemChanged(ValueChangedEvent e) { // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info - var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); + var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.Value.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); if (e.NewValue?.Ruleset != null) - Ruleset.Value = e.NewValue.Ruleset; + Ruleset.Value = e.NewValue.Ruleset.Value; previewTrackManager.StopAnyPlaying(this); } @@ -206,7 +207,7 @@ namespace osu.Game.Screens.Multi.Match return; // Try to retrieve the corresponding local beatmap - var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.OnlineBeatmapID); + var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.Value.OnlineBeatmapID); if (localBeatmap != null) Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 88c6fc5e2e..3afacf2f31 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -50,10 +50,10 @@ namespace osu.Game.Screens.Multi.Play bool failed = false; // Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem - if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.OnlineBeatmapID) + if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.Value.OnlineBeatmapID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); - if (ruleset.Value.ID != playlistItem.Ruleset.ID) + if (ruleset.Value.ID != playlistItem.Ruleset.Value.ID) throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 6ba4157797..ff0544d227 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -38,8 +39,8 @@ namespace osu.Game.Screens.Select { var item = new PlaylistItem { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = Ruleset.Value, + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Ruleset.Value }, RulesetID = Ruleset.Value.ID ?? 0 }; @@ -60,8 +61,8 @@ namespace osu.Game.Screens.Select if (CurrentItem.Value != null) { - Ruleset.Value = CurrentItem.Value.Ruleset; - Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); + Ruleset.Value = CurrentItem.Value.Ruleset.Value; + Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap.Value); Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); } From be2d1c6b4305897f12a1b25802bf1e0cfdc71fbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 18:35:31 +0900 Subject: [PATCH 38/52] 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 25bde037db..494842f38f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 389fbe8210..50d8c25b11 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3ed25360c5..e56fc41b07 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 75bef15583aecec6bb2f4d0d223a7e0b64a5fd5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Feb 2020 18:48:28 +0900 Subject: [PATCH 39/52] Remove "current" multiplayer room item --- .../Multiplayer/TestSceneMatchBeatmapPanel.cs | 12 ------- osu.Game/Online/Multiplayer/PlaylistItem.cs | 12 ++++++- osu.Game/Online/Multiplayer/Room.cs | 24 ++------------ .../Screens/Multi/Components/BeatmapTitle.cs | 8 +++-- .../Multi/Components/BeatmapTypeInfo.cs | 32 ++++++++++-------- .../Screens/Multi/Components/ModeTypeInfo.cs | 13 +++++--- .../Components/MultiplayerBackgroundSprite.cs | 14 ++++++-- .../Screens/Multi/Match/Components/Header.cs | 16 +++++++-- .../Screens/Multi/Match/Components/Info.cs | 16 +++++++-- .../Match/Components/MatchBeatmapPanel.cs | 11 +++++-- .../Screens/Multi/Match/MatchSubScreen.cs | 33 ++++++++++++------- .../Screens/Multi/MultiplayerComposite.cs | 3 -- osu.Game/Screens/Select/MatchSongSelect.cs | 16 +++++---- 13 files changed, 123 insertions(+), 87 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs index 84ab6f9ccc..f014b08325 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs @@ -7,7 +7,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Match.Components; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Framework.Allocation; @@ -38,16 +37,5 @@ namespace osu.Game.Tests.Visual.Multiplayer Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2109801 } } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1922035 } } }); } - - protected override void LoadComplete() - { - base.LoadComplete(); - - AddStep("Select random beatmap", () => - { - Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)]; - previewTrackManager.StopAnyPlaying(this); - }); - } } } diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index e243cfca08..69e1f0db13 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -12,7 +13,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Multiplayer { - public class PlaylistItem + public class PlaylistItem : IEquatable { [JsonProperty("id")] public int ID { get; set; } @@ -90,5 +91,14 @@ namespace osu.Game.Online.Multiplayer public bool ShouldSerializeID() => false; public bool ShouldSerializeapiBeatmap() => false; + + public bool Equals(PlaylistItem other) => ID == other?.ID && BeatmapID == other.BeatmapID && RulesetID == other.RulesetID; + + public override int GetHashCode() + { + // ReSharper disable NonReadonlyMemberInGetHashCode + return HashCode.Combine(ID, BeatmapID, RulesetID); + // ReSharper restore NonReadonlyMemberInGetHashCode + } } } diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 53089897f7..1f123de1a3 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -31,10 +31,6 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("playlist")] public BindableList Playlist { get; private set; } = new BindableList(); - [Cached] - [JsonIgnore] - public Bindable CurrentItem { get; private set; } = new Bindable(); - [Cached] [JsonProperty("channel_id")] public Bindable ChannelId { get; private set; } = new Bindable(); @@ -70,18 +66,6 @@ namespace osu.Game.Online.Multiplayer [Cached] public Bindable ParticipantCount { get; private set; } = new Bindable(); - public Room() - { - Playlist.ItemsAdded += updateCurrent; - Playlist.ItemsRemoved += updateCurrent; - updateCurrent(Playlist); - } - - private void updateCurrent(IEnumerable playlist) - { - CurrentItem.Value = playlist.FirstOrDefault(); - } - // todo: TEMPORARY [JsonProperty("participant_count")] private int? participantCount @@ -136,11 +120,9 @@ namespace osu.Game.Online.Multiplayer if (DateTimeOffset.Now >= EndDate.Value) Status.Value = new RoomStatusEnded(); - // Todo: Temporary, should only remove/add new items (requires framework changes) - if (Playlist.Count == 0) - Playlist.AddRange(other.Playlist); - else if (other.Playlist.Count > 0) - Playlist.First().ID = other.Playlist.First().ID; + foreach (var removedItem in Playlist.Except(other.Playlist).ToArray()) + Playlist.Remove(removedItem); + Playlist.AddRange(other.Playlist.Except(Playlist).ToArray()); Position = other.Position; } diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index b991a3d68d..baf11dfe0d 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -25,7 +26,10 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load() { - CurrentItem.BindValueChanged(_ => updateText(), true); + Playlist.ItemsAdded += _ => updateText(); + Playlist.ItemsRemoved += _ => updateText(); + + updateText(); } private float textSize = OsuFont.DEFAULT_FONT_SIZE; @@ -54,7 +58,7 @@ namespace osu.Game.Screens.Multi.Components textFlow.Clear(); - var beatmap = CurrentItem.Value?.Beatmap; + var beatmap = Playlist.FirstOrDefault()?.Beatmap; if (beatmap == null) { diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index 21b228bb5c..a1334101b8 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,6 +13,8 @@ namespace osu.Game.Screens.Multi.Components { public class BeatmapTypeInfo : MultiplayerComposite { + private LinkFlowContainer beatmapAuthor; + public BeatmapTypeInfo() { AutoSizeAxes = Axes.Both; @@ -20,8 +23,6 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load() { - LinkFlowContainer beatmapAuthor; - InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -50,18 +51,23 @@ namespace osu.Game.Screens.Multi.Components } }; - CurrentItem.BindValueChanged(item => + Playlist.ItemsAdded += _ => updateInfo(); + Playlist.ItemsRemoved += _ => updateInfo(); + + updateInfo(); + } + + private void updateInfo() + { + beatmapAuthor.Clear(); + + var beatmap = Playlist.FirstOrDefault()?.Beatmap; + + if (beatmap != null) { - beatmapAuthor.Clear(); - - var beatmap = item.NewValue?.Beatmap; - - if (beatmap != null) - { - beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); - beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author); - } - }, true); + beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); + beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author); + } } } } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 5465463888..258541bbd6 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -1,11 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.Drawables; -using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.Multi.Components @@ -46,13 +46,18 @@ namespace osu.Game.Screens.Multi.Components }, }; - CurrentItem.BindValueChanged(item => updateBeatmap(item.NewValue), true); - Type.BindValueChanged(type => gameTypeContainer.Child = new DrawableGameType(type.NewValue) { Size = new Vector2(height) }, true); + + Playlist.ItemsAdded += _ => updateBeatmap(); + Playlist.ItemsRemoved += _ => updateBeatmap(); + + updateBeatmap(); } - private void updateBeatmap(PlaylistItem item) + private void updateBeatmap() { + var item = Playlist.FirstOrDefault(); + if (item?.Beatmap != null) { drawableRuleset.FadeIn(transition_duration); diff --git a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs index 9a1a482699..5e2f2e530a 100644 --- a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs +++ b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps.Drawables; @@ -10,6 +11,7 @@ namespace osu.Game.Screens.Multi.Components public class MultiplayerBackgroundSprite : MultiplayerComposite { private readonly BeatmapSetCoverType beatmapSetCoverType; + private UpdateableBeatmapBackgroundSprite sprite; public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) { @@ -19,11 +21,17 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load() { - UpdateableBeatmapBackgroundSprite sprite; - InternalChild = sprite = CreateBackgroundSprite(); - CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true); + Playlist.ItemsAdded += _ => updateBeatmap(); + Playlist.ItemsRemoved += _ => updateBeatmap(); + + updateBeatmap(); + } + + private void updateBeatmap() + { + sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value; } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index 991060cac0..cf1eb6b6ed 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -33,6 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components public Action RequestBeatmapSelection; private MatchBeatmapPanel beatmapPanel; + private ModDisplay modDisplay; public Header() { @@ -44,7 +45,6 @@ namespace osu.Game.Screens.Multi.Match.Components private void load(OsuColour colours) { BeatmapSelectButton beatmapButton; - ModDisplay modDisplay; InternalChildren = new Drawable[] { @@ -120,9 +120,12 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true); - beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); + + Playlist.ItemsAdded += _ => updateMods(); + Playlist.ItemsRemoved += _ => updateMods(); + + updateMods(); } protected override void LoadComplete() @@ -131,6 +134,13 @@ namespace osu.Game.Screens.Multi.Match.Components ShowBeatmapPanel.BindValueChanged(value => beatmapPanel.FadeTo(value.NewValue ? 1 : 0, 200, Easing.OutQuint), true); } + private void updateMods() + { + var item = Playlist.FirstOrDefault(); + + modDisplay.Current.Value = item?.RequiredMods?.ToArray() ?? Array.Empty(); + } + private class BeatmapSelectButton : HeaderButton { [Resolved(typeof(Room), nameof(Room.RoomID))] diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs index f0b54baa4c..a320b08cc4 100644 --- a/osu.Game/Screens/Multi/Match/Components/Info.cs +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,6 +19,8 @@ namespace osu.Game.Screens.Multi.Match.Components { public Action OnStart; + private ReadyButton readyButton; + public Info() { RelativeSizeAxes = Axes.X; @@ -27,7 +30,6 @@ namespace osu.Game.Screens.Multi.Match.Components [BackgroundDependencyLoader] private void load() { - ReadyButton readyButton; HostInfo hostInfo; InternalChildren = new Drawable[] @@ -89,9 +91,17 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap.Value, true); - hostInfo.Host.BindTo(Host); + + Playlist.ItemsAdded += _ => updateBeatmap(); + Playlist.ItemsRemoved += _ => updateBeatmap(); + + updateBeatmap(); + } + + private void updateBeatmap() + { + readyButton.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value; } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs index f67f6d9f43..c8de066caa 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs @@ -1,10 +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.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; @@ -32,10 +32,13 @@ namespace osu.Game.Screens.Multi.Match.Components [BackgroundDependencyLoader] private void load() { - CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap.Value), true); + Playlist.ItemsAdded += _ => loadNewPanel(); + Playlist.ItemsRemoved += _ => loadNewPanel(); + + loadNewPanel(); } - private void loadNewPanel(BeatmapInfo beatmap) + private void loadNewPanel() { loadCancellation?.Cancel(); request?.Cancel(); @@ -44,6 +47,8 @@ namespace osu.Game.Screens.Multi.Match.Components panel?.Expire(); panel = null; + var beatmap = Playlist.FirstOrDefault()?.Beatmap.Value; + if (beatmap?.OnlineBeatmapID == null) return; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 9165ef6d0f..890664e99b 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -41,9 +41,6 @@ namespace osu.Game.Screens.Multi.Match [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } - [Resolved(typeof(Room))] - protected Bindable CurrentItem { get; private set; } - [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -53,6 +50,7 @@ namespace osu.Game.Screens.Multi.Match [Resolved(CanBeNull = true)] private OsuGame game { get; set; } + private readonly Bindable selectedItem = new Bindable(); private MatchLeaderboard leaderboard; public MatchSubScreen(Room room) @@ -166,7 +164,16 @@ namespace osu.Game.Screens.Multi.Match { base.LoadComplete(); - CurrentItem.BindValueChanged(currentItemChanged, true); + Playlist.ItemsAdded += _ => updateSelectedItem(); + Playlist.ItemsRemoved += _ => updateSelectedItem(); + + updateSelectedItem(); + } + + private void updateSelectedItem() + { + selectedItem.Value = Playlist.FirstOrDefault(); + currentItemChanged(); } public override bool OnExiting(IScreen next) @@ -181,16 +188,18 @@ namespace osu.Game.Screens.Multi.Match /// /// Handles propagation of the current playlist item's content to game-wide mechanisms. /// - private void currentItemChanged(ValueChangedEvent e) + private void currentItemChanged() { + var item = selectedItem.Value; + // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info - var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.Value.OnlineBeatmapID); + var localBeatmap = item?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == item.Beatmap.Value.OnlineBeatmapID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); - Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); + Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty(); - if (e.NewValue?.Ruleset != null) - Ruleset.Value = e.NewValue.Ruleset.Value; + if (item?.Ruleset != null) + Ruleset.Value = item.Ruleset.Value; previewTrackManager.StopAnyPlaying(this); } @@ -203,11 +212,11 @@ namespace osu.Game.Screens.Multi.Match if (Beatmap.Value != beatmapManager.DefaultBeatmap) return; - if (CurrentItem.Value == null) + if (selectedItem.Value == null) return; // Try to retrieve the corresponding local beatmap - var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.Value.OnlineBeatmapID); + var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == selectedItem.Value.Beatmap.Value.OnlineBeatmapID); if (localBeatmap != null) Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); @@ -224,7 +233,7 @@ namespace osu.Game.Screens.Multi.Match { default: case GameTypeTimeshift _: - multiplayer?.Start(() => new TimeshiftPlayer(CurrentItem.Value) + multiplayer?.Start(() => new TimeshiftPlayer(selectedItem.Value) { Exited = () => leaderboard.RefreshScores() }); diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index 8c09d576ff..cf4a189951 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -31,9 +31,6 @@ namespace osu.Game.Screens.Multi [Resolved(typeof(Room))] protected BindableList Playlist { get; private set; } - [Resolved(typeof(Room))] - protected Bindable CurrentItem { get; private set; } - [Resolved(typeof(Room))] protected Bindable> Participants { get; private set; } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index ff0544d227..251456bf0d 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -22,11 +22,11 @@ namespace osu.Game.Screens.Select public string ShortTitle => "song selection"; public override string Title => ShortTitle.Humanize(); - [Resolved(typeof(Room))] - protected Bindable CurrentItem { get; private set; } - public override bool AllowEditing => false; + [Resolved(typeof(Room), nameof(Room.Playlist))] + protected BindableList Playlist { get; private set; } + [Resolved] private BeatmapManager beatmaps { get; set; } @@ -59,11 +59,13 @@ namespace osu.Game.Screens.Select if (base.OnExiting(next)) return true; - if (CurrentItem.Value != null) + var firstItem = Playlist.FirstOrDefault(); + + if (firstItem != null) { - Ruleset.Value = CurrentItem.Value.Ruleset.Value; - Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap.Value); - Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); + Ruleset.Value = firstItem.Ruleset.Value; + Beatmap.Value = beatmaps.GetWorkingBeatmap(firstItem.Beatmap.Value); + Mods.Value = firstItem.RequiredMods?.ToArray() ?? Array.Empty(); } return false; From bce9c8f3f30352ffa9b29d705fd72557d98b55f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Feb 2020 18:59:15 +0900 Subject: [PATCH 40/52] Make room participants into a bindable list --- .../Multiplayer/TestSceneMatchParticipants.cs | 6 +++--- osu.Game/Online/Multiplayer/Room.cs | 7 +++++-- .../Multi/Match/Components/Participants.cs | 13 +++++++++---- .../Screens/Multi/MultiplayerComposite.cs | 3 +-- osu.Game/Users/UserPanel.cs | 19 ++++++++++--------- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs index 1ac914e27d..a6f47961e9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Add(new Participants { RelativeSizeAxes = Axes.Both }); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); - AddStep(@"set users", () => Room.Participants.Value = new[] + AddStep(@"set users", () => Room.Participants.AddRange(new[] { new User { @@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.Multiplayer CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg", IsSupporter = true, }, - }); + })); AddStep(@"set max", () => Room.MaxParticipants.Value = 10); - AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty()); + AddStep(@"clear users", () => Room.Participants.Clear()); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); } } diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 53089897f7..0f4abeafd4 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -65,7 +65,7 @@ namespace osu.Game.Online.Multiplayer [Cached] [JsonIgnore] - public Bindable> Participants { get; private set; } = new Bindable>(Enumerable.Empty()); + public BindableList Participants { get; private set; } = new BindableList(); [Cached] public Bindable ParticipantCount { get; private set; } = new Bindable(); @@ -130,7 +130,6 @@ namespace osu.Game.Online.Multiplayer Type.Value = other.Type.Value; MaxParticipants.Value = other.MaxParticipants.Value; ParticipantCount.Value = other.ParticipantCount.Value; - Participants.Value = other.Participants.Value.ToArray(); EndDate.Value = other.EndDate.Value; if (DateTimeOffset.Now >= EndDate.Value) @@ -142,6 +141,10 @@ namespace osu.Game.Online.Multiplayer else if (other.Playlist.Count > 0) Playlist.First().ID = other.Playlist.First().ID; + foreach (var removedItem in Participants.Except(other.Participants).ToArray()) + Participants.Remove(removedItem); + Participants.AddRange(other.Participants.Except(Participants).ToArray()); + Position = other.Position; } diff --git a/osu.Game/Screens/Multi/Match/Components/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs index ad38ec6a99..00d2f3e150 100644 --- a/osu.Game/Screens/Multi/Match/Components/Participants.cs +++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs @@ -51,9 +51,9 @@ namespace osu.Game.Screens.Multi.Match.Components }, }; - Participants.BindValueChanged(participants => + Participants.ItemsAdded += users => { - usersFlow.Children = participants.NewValue.Select(u => + usersFlow.AddRange(users.Select(u => { var panel = new UserPanel(u) { @@ -65,8 +65,13 @@ namespace osu.Game.Screens.Multi.Match.Components panel.OnLoadComplete += d => d.FadeInFromZero(60); return panel; - }).ToList(); - }, true); + }).ToList()); + }; + + Participants.ItemsRemoved += users => + { + usersFlow.RemoveAll(p => users.Contains(p.User)); + }; } } } diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs index 8c09d576ff..346f78f2c6 100644 --- a/osu.Game/Screens/Multi/MultiplayerComposite.cs +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -35,7 +34,7 @@ namespace osu.Game.Screens.Multi protected Bindable CurrentItem { get; private set; } [Resolved(typeof(Room))] - protected Bindable> Participants { get; private set; } + protected BindableList Participants { get; private set; } [Resolved(typeof(Room))] protected Bindable ParticipantCount { get; private set; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 6ddbc13a06..6f34466e94 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -26,11 +26,12 @@ namespace osu.Game.Users { public class UserPanel : OsuClickableContainer, IHasContextMenu { - private readonly User user; private const float height = 100; private const float content_padding = 10; private const float status_height = 30; + public readonly User User; + [Resolved(canBeNull: true)] private OsuColour colours { get; set; } @@ -54,7 +55,7 @@ namespace osu.Game.Users if (user == null) throw new ArgumentNullException(nameof(user)); - this.user = user; + User = user; Height = height - status_height; } @@ -86,7 +87,7 @@ namespace osu.Game.Users RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - User = user, + User = User, }, 300, 5000) { RelativeSizeAxes = Axes.Both, @@ -106,7 +107,7 @@ namespace osu.Game.Users new UpdateableAvatar { Size = new Vector2(height - status_height - content_padding * 2), - User = user, + User = User, Masking = true, CornerRadius = 5, OpenOnClick = { Value = false }, @@ -125,7 +126,7 @@ namespace osu.Game.Users { new OsuSpriteText { - Text = user.Username, + Text = User.Username, Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18, italics: true), }, infoContainer = new FillFlowContainer @@ -138,7 +139,7 @@ namespace osu.Game.Users Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new UpdateableFlag(user.Country) + new UpdateableFlag(User.Country) { Width = 30f, RelativeSizeAxes = Axes.Y, @@ -191,12 +192,12 @@ namespace osu.Game.Users } }); - if (user.IsSupporter) + if (User.IsSupporter) { infoContainer.Add(new SupporterIcon { Height = 20f, - SupportLevel = user.SupportLevel + SupportLevel = User.SupportLevel }); } @@ -206,7 +207,7 @@ namespace osu.Game.Users base.Action = ViewProfile = () => { Action?.Invoke(); - profile?.ShowUser(user); + profile?.ShowUser(User); }; } From bc1c4f6b583eae9b2b6020f0f896cba6c21e1907 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 13 Feb 2020 19:04:23 +0900 Subject: [PATCH 41/52] Add missing null-allowance --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c9131883bb..1f52a4481b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -55,7 +55,7 @@ namespace osu.Game.Online.Leaderboards private List statisticsLabels; - [Resolved] + [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true) From e181a95c855d1ba6562219a7df9776955f1a7a4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Feb 2020 22:28:37 +0900 Subject: [PATCH 42/52] Remove unused using --- osu.Game/Online/Multiplayer/Room.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index ff11a74a1a..400afb39a1 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; using osu.Framework.Allocation; From 50899ddccba2e8dfc89d0282ee5a547728c53f3e Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Fri, 14 Feb 2020 03:19:25 +0100 Subject: [PATCH 43/52] Use Span for OsuColour.FromHex --- osu.Game/Graphics/OsuColour.cs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index c8298543a1..59dd823266 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Game.Beatmaps; using osuTK.Graphics; @@ -14,41 +15,40 @@ namespace osu.Game.Graphics public static Color4 FromHex(string hex) { - if (hex[0] == '#') - hex = hex.Substring(1); + var hexSpan = hex[0] == '#' ? hex.AsSpan().Slice(1) : hex.AsSpan(); - switch (hex.Length) + switch (hexSpan.Length) { default: throw new ArgumentException(@"Invalid hex string length!"); case 3: return new Color4( - (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), + (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17), + (byte)(byte.Parse(hexSpan.Slice(1, 1), NumberStyles.HexNumber) * 17), + (byte)(byte.Parse(hexSpan.Slice(2, 1), NumberStyles.HexNumber) * 17), 255); case 6: return new Color4( - Convert.ToByte(hex.Substring(0, 2), 16), - Convert.ToByte(hex.Substring(2, 2), 16), - Convert.ToByte(hex.Substring(4, 2), 16), + byte.Parse(hexSpan.Slice(0, 2), NumberStyles.HexNumber), + byte.Parse(hexSpan.Slice(2, 2), NumberStyles.HexNumber), + byte.Parse(hexSpan.Slice(4, 2), NumberStyles.HexNumber), 255); case 4: return new Color4( - (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), - (byte)(Convert.ToByte(hex.Substring(3, 1), 16) * 17)); + (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17), + (byte)(byte.Parse(hexSpan.Slice(1, 1), NumberStyles.HexNumber) * 17), + (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17), + (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17)); case 8: return new Color4( - Convert.ToByte(hex.Substring(0, 2), 16), - Convert.ToByte(hex.Substring(2, 2), 16), - Convert.ToByte(hex.Substring(4, 2), 16), - Convert.ToByte(hex.Substring(6, 2), 16)); + byte.Parse(hexSpan.Slice(0, 2), NumberStyles.HexNumber), + byte.Parse(hexSpan.Slice(2, 2), NumberStyles.HexNumber), + byte.Parse(hexSpan.Slice(4, 2), NumberStyles.HexNumber), + byte.Parse(hexSpan.Slice(6, 2), NumberStyles.HexNumber)); } } From 3a0b2508d42b1d09da1aa30bbc356d88b5b4f76d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 15:01:30 +0900 Subject: [PATCH 44/52] Fix possible nullrefs --- osu.Game/Overlays/Direct/PanelDownloadButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Direct/PanelDownloadButton.cs b/osu.Game/Overlays/Direct/PanelDownloadButton.cs index ed44f1e960..1b3657f010 100644 --- a/osu.Game/Overlays/Direct/PanelDownloadButton.cs +++ b/osu.Game/Overlays/Direct/PanelDownloadButton.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Direct [BackgroundDependencyLoader(true)] private void load(OsuGame game, BeatmapManager beatmaps) { - if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) + if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false) { button.Enabled.Value = false; button.TooltipText = "this beatmap is currently not available for download."; @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Direct break; case DownloadState.LocallyAvailable: - game.PresentBeatmap(BeatmapSet.Value); + game?.PresentBeatmap(BeatmapSet.Value); break; default: From eb14dbcd77ffc141ff717f4307df40028027d451 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 15:01:45 +0900 Subject: [PATCH 45/52] Initial implementation of rearrangeable playlist --- .../TestSceneDrawableRoomPlaylist.cs | 86 +++++ .../Screens/Multi/DrawableRoomPlaylist.cs | 81 ++++ .../Screens/Multi/DrawableRoomPlaylistItem.cs | 347 ++++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs create mode 100644 osu.Game/Screens/Multi/DrawableRoomPlaylist.cs create mode 100644 osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs new file mode 100644 index 0000000000..b906ce82e9 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -0,0 +1,86 @@ +// 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; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Multi; +using osuTK; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneDrawableRoomPlaylist : OsuTestScene + { + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + [Resolved] + private RulesetStore rulesetStore { get; set; } + + private DrawableRoomPlaylist playlist; + + [Test] + public void TestItemsCanNotBeRemovedOrSelectedFromNonEditableAndNonSelectablePlaylist() + { + createPlaylist(false, false); + } + + [Test] + public void TestItemsCanBeRemovedFromEditablePlaylist() + { + createPlaylist(true, false); + } + + [Test] + public void TestItemsCanBeSelectedInSelectablePlaylist() + { + createPlaylist(false, true); + } + + [Test] + public void TestItemsCanBeSelectedAndRemovedFromEditableAndSelectablePlaylist() + { + createPlaylist(true, true); + } + + private void createPlaylist(bool allowEdit, bool allowSelection) => AddStep("create playlist", () => + { + Child = playlist = new DrawableRoomPlaylist(allowEdit, allowSelection) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + var beatmapSets = beatmapManager.GetAllUsableBeatmapSets(); + var rulesets = rulesetStore.AvailableRulesets.ToList(); + + for (int i = 0; i < 20; i++) + { + var set = beatmapSets[RNG.Next(0, beatmapSets.Count)]; + var beatmap = set.Beatmaps[RNG.Next(0, set.Beatmaps.Count)]; + + beatmap.BeatmapSet = set; + beatmap.Metadata = set.Metadata; + + playlist.Items.Add(new PlaylistItem + { + Beatmap = { Value = beatmap }, + Ruleset = { Value = rulesets[RNG.Next(0, rulesets.Count)] }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + } +} diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs new file mode 100644 index 0000000000..b42a8575ec --- /dev/null +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Screens.Multi +{ + public class DrawableRoomPlaylist : RearrangeableListContainer + { + public readonly Bindable SelectedItem = new Bindable(); + + private readonly bool allowEdit; + private readonly bool allowSelection; + + public DrawableRoomPlaylist(bool allowEdit, bool allowSelection) + { + this.allowEdit = allowEdit; + this.allowSelection = allowSelection; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(item => + { + if (item.OldValue != null && ItemMap.TryGetValue(item.OldValue, out var oldItem)) + ((DrawableRoomPlaylistItem)oldItem).Deselect(); + + if (item.NewValue != null && ItemMap.TryGetValue(item.NewValue, out var newItem)) + ((DrawableRoomPlaylistItem)newItem).Select(); + }, true); + + Items.ItemsRemoved += items => + { + if (items.Any(i => i == SelectedItem.Value)) + SelectedItem.Value = null; + }; + } + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer + { + ScrollbarVisible = false + }; + + protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> + { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(0, 2) + }; + + protected override RearrangeableListItem CreateDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection) + { + RequestSelection = requestSelection, + RequestDeletion = requestDeletion + }; + + private void requestSelection(PlaylistItem item) => SelectedItem.Value = item; + + private void requestDeletion(PlaylistItem item) + { + if (SelectedItem.Value == item) + { + if (Items.Count == 1) + SelectedItem.Value = null; + else + SelectedItem.Value = Items.GetNext(item) ?? Items[^2]; + } + + Items.Remove(item); + } + } +} diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs new file mode 100644 index 0000000000..d2788a5c4b --- /dev/null +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -0,0 +1,347 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi +{ + public class DrawableRoomPlaylistItem : RearrangeableListItem + { + public Action RequestSelection; + public Action RequestDeletion; + + private Container maskingContainer; + private Container difficultyIconContainer; + private LinkFlowContainer beatmapText; + private LinkFlowContainer authorText; + private ItemHandle handle; + private ModDisplay modDisplay; + + private readonly Bindable beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + private readonly BindableList requiredMods = new BindableList(); + + private readonly PlaylistItem item; + private readonly bool allowEdit; + private readonly bool allowSelection; + + public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection) + : base(item) + { + this.item = item; + this.allowEdit = allowEdit; + this.allowSelection = allowSelection; + + RelativeSizeAxes = Axes.X; + Height = 50; + + beatmap.BindTo(item.Beatmap); + ruleset.BindTo(item.Ruleset); + requiredMods.BindTo(item.RequiredMods); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Alpha = allowEdit ? 1 : 0, + Child = handle = new ItemHandle + { + Size = new Vector2(12), + AlwaysPresent = true, + Alpha = 0, + }, + }, + maskingContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + BorderColour = colours.Yellow, + Children = new Drawable[] + { + new Box // A transparent box that forces the border to be drawn if the panel background is opaque + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new PanelBackground + { + RelativeSizeAxes = Axes.Both, + Beatmap = { BindTarget = beatmap } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 8 }, + Spacing = new Vector2(8, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + difficultyIconContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + beatmapText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, + modDisplay = new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.4f), + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded + } + } + } + } + } + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + X = -18, + Children = new Drawable[] + { + new IconButton + { + Icon = FontAwesome.Solid.MinusSquare, + Alpha = allowEdit ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Model), + }, + new PanelDownloadButton(item.Beatmap.Value.BeatmapSet) + { + Size = new Vector2(50, 30), + Alpha = allowEdit ? 0 : 1 + } + } + } + } + } + }, + }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(_ => scheduleRefresh()); + ruleset.BindValueChanged(_ => scheduleRefresh()); + + requiredMods.ItemsAdded += _ => scheduleRefresh(); + requiredMods.ItemsRemoved += _ => scheduleRefresh(); + + refresh(); + } + + private ScheduledDelegate scheduledRefresh; + + private void scheduleRefresh() + { + scheduledRefresh?.Cancel(); + scheduledRefresh = Schedule(refresh); + } + + private void refresh() + { + difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) }; + + beatmapText.Clear(); + beatmapText.AddLink(item.Beatmap.ToString(), LinkAction.OpenBeatmap, item.Beatmap.Value.OnlineBeatmapID.ToString()); + + authorText.Clear(); + + if (item.Beatmap?.Value?.Metadata?.Author != null) + { + authorText.AddText("mapped by "); + authorText.AddUserLink(item.Beatmap.Value?.Metadata.Author); + } + + modDisplay.Current.Value = requiredMods.ToArray(); + } + + protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; + + protected override bool OnHover(HoverEvent e) + { + handle.UpdateHoverState(true); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false); + + protected override bool OnClick(ClickEvent e) + { + if (allowSelection) + RequestSelection?.Invoke(Model); + return true; + } + + public void Select() => maskingContainer.BorderThickness = 5; + + public void Deselect() => maskingContainer.BorderThickness = 0; + + // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap + private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) + { + public readonly Bindable Beatmap = new Bindable(); + + public PanelBackground() + { + InternalChildren = new Drawable[] + { + new UpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Beatmap = { BindTarget = Beatmap } + }, + new Container + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle + Shear = new Vector2(0.8f, 0), + Alpha = 0.5f, + Children = new[] + { + // The left half with no gradient applied + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = Color4.Black, + Width = 0.4f, + }, + // Piecewise-linear gradient with 3 segments to make it appear smoother + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), + Width = 0.05f, + X = 0.4f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Width = 0.2f, + X = 0.45f, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), + Width = 0.05f, + X = 0.65f, + }, + } + } + }; + } + } + + private class ItemHandle : SpriteIcon + { + public bool HandlingDrag { get; private set; } + private bool isHovering; + + public ItemHandle() + { + Margin = new MarginPadding { Horizontal = 5 }; + + Icon = FontAwesome.Solid.Bars; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + base.OnMouseDown(e); + + HandlingDrag = true; + UpdateHoverState(isHovering); + + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + + HandlingDrag = false; + UpdateHoverState(isHovering); + } + + public void UpdateHoverState(bool hovering) + { + isHovering = hovering; + + if (isHovering || HandlingDrag) + this.FadeIn(100); + else + this.FadeOut(100); + } + } + } +} From eb75d26c8f5c3365fda1965401f4df77fb900078 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 15:36:16 +0900 Subject: [PATCH 46/52] Extract common rearrangeable list design --- .../OsuRearrangeableListContainer.cs | 26 +++ .../Containers/OsuRearrangeableListItem.cs | 149 ++++++++++++++++++ osu.Game/Overlays/Music/Playlist.cs | 12 +- osu.Game/Overlays/Music/PlaylistItem.cs | 121 ++------------ 4 files changed, 190 insertions(+), 118 deletions(-) create mode 100644 osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs create mode 100644 osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs new file mode 100644 index 0000000000..47aed1c500 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.Containers +{ + public abstract class OsuRearrangeableListContainer : RearrangeableListContainer + { + /// + /// Whether any item is currently being dragged. Used to hide other items' drag handles. + /// + private readonly BindableBool playlistDragActive = new BindableBool(); + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d => + { + d.PlaylistDragActive.BindTo(playlistDragActive); + }); + + protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item); + } +} diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs new file mode 100644 index 0000000000..f75a3330e2 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.Containers +{ + public abstract class OsuRearrangeableListItem : RearrangeableListItem + { + public const float FADE_DURATION = 100; + + /// + /// Whether any item is currently being dragged. Used to hide other items' drag handles. + /// + public readonly BindableBool PlaylistDragActive = new BindableBool(); + + private Color4 handleColour = Color4.White; + + protected Color4 HandleColour + { + get => handleColour; + set + { + if (handleColour == value) + return; + + handleColour = value; + + if (handle != null) + handle.Colour = value; + } + } + + private PlaylistItemHandle handle; + + protected OsuRearrangeableListItem(TModel item) + : base(item) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 5 }, + Child = handle = new PlaylistItemHandle + { + Size = new Vector2(12), + Colour = HandleColour, + AlwaysPresent = true, + Alpha = 0 + } + }, + CreateContent() + } + }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }; + } + + protected override bool OnDragStart(DragStartEvent e) + { + if (!base.OnDragStart(e)) + return false; + + PlaylistDragActive.Value = true; + return true; + } + + protected override void OnDragEnd(DragEndEvent e) + { + PlaylistDragActive.Value = false; + base.OnDragEnd(e); + } + + protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; + + protected override bool OnHover(HoverEvent e) + { + handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false); + + protected abstract Drawable CreateContent(); + + private class PlaylistItemHandle : SpriteIcon + { + public bool HandlingDrag { get; private set; } + private bool isHovering; + + public PlaylistItemHandle() + { + Icon = FontAwesome.Solid.Bars; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + base.OnMouseDown(e); + + HandlingDrag = true; + UpdateHoverState(isHovering); + + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + + HandlingDrag = false; + UpdateHoverState(isHovering); + } + + public void UpdateHoverState(bool hovering) + { + isHovering = hovering; + + if (isHovering || HandlingDrag) + this.FadeIn(FADE_DURATION); + else + this.FadeOut(FADE_DURATION); + } + } + } +} diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 1ba568443d..621a533dd6 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -12,17 +12,12 @@ using osuTK; namespace osu.Game.Overlays.Music { - public class Playlist : RearrangeableListContainer + public class Playlist : OsuRearrangeableListContainer { public Action RequestSelection; public readonly Bindable SelectedSet = new Bindable(); - /// - /// Whether any item is currently being dragged. Used to hide other items' drag handles. - /// - private readonly BindableBool playlistDragActive = new BindableBool(); - public new MarginPadding Padding { get => base.Padding; @@ -33,15 +28,12 @@ namespace osu.Game.Overlays.Music public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new PlaylistItem(item) + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapSetInfo item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, - PlaylistDragActive = { BindTarget = playlistDragActive }, RequestSelection = set => RequestSelection?.Invoke(set) }; - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer> { Spacing = new Vector2(0, 3), diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 0569261867..8cafbc694a 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -14,36 +14,27 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistItem : RearrangeableListItem, IFilterable + public class PlaylistItem : OsuRearrangeableListItem, IFilterable { - private const float fade_duration = 100; - - public BindableBool PlaylistDragActive = new BindableBool(); - public readonly Bindable SelectedSet = new Bindable(); public Action RequestSelection; - private PlaylistItemHandle handle; private TextFlowContainer text; private IEnumerable titleSprites; private ILocalisedBindableString titleBind; private ILocalisedBindableString artistBind; - private Color4 hoverColour; + private Color4 selectedColour; private Color4 artistColour; public PlaylistItem(BeatmapSetInfo item) : base(item) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Left = 5 }; FilterTerms = item.Metadata.SearchableTerms; @@ -52,42 +43,12 @@ namespace osu.Game.Overlays.Music [BackgroundDependencyLoader] private void load(OsuColour colours, LocalisationManager localisation) { - hoverColour = colours.Yellow; + selectedColour = colours.Yellow; artistColour = colours.Gray9; - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - handle = new PlaylistItemHandle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(12), - Colour = colours.Gray5, - AlwaysPresent = true, - Alpha = 0 - }, - text = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 5 }, - }, - } - }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }; + HandleColour = colours.Gray5; titleBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title))); artistBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist))); - - artistBind.BindValueChanged(_ => recreateText(), true); } protected override void LoadComplete() @@ -100,10 +61,18 @@ namespace osu.Game.Overlays.Music return; foreach (Drawable s in titleSprites) - s.FadeColour(set.NewValue == Model ? hoverColour : Color4.White, fade_duration); + s.FadeColour(set.NewValue == Model ? selectedColour : Color4.White, FADE_DURATION); }, true); + + artistBind.BindValueChanged(_ => recreateText(), true); } + protected override Drawable CreateContent() => text = new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }; + private void recreateText() { text.Clear(); @@ -125,31 +94,6 @@ namespace osu.Game.Overlays.Music return true; } - protected override bool OnDragStart(DragStartEvent e) - { - if (!base.OnDragStart(e)) - return false; - - PlaylistDragActive.Value = true; - return true; - } - - protected override void OnDragEnd(DragEndEvent e) - { - PlaylistDragActive.Value = false; - base.OnDragEnd(e); - } - - protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; - - protected override bool OnHover(HoverEvent e) - { - handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false); - public IEnumerable FilterTerms { get; } private bool matching = true; @@ -168,44 +112,5 @@ namespace osu.Game.Overlays.Music } public bool FilteringActive { get; set; } - - private class PlaylistItemHandle : SpriteIcon - { - public bool HandlingDrag { get; private set; } - private bool isHovering; - - public PlaylistItemHandle() - { - Icon = FontAwesome.Solid.Bars; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - base.OnMouseDown(e); - - HandlingDrag = true; - UpdateHoverState(isHovering); - - return false; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - base.OnMouseUp(e); - - HandlingDrag = false; - UpdateHoverState(isHovering); - } - - public void UpdateHoverState(bool hovering) - { - isHovering = hovering; - - if (isHovering || HandlingDrag) - this.FadeIn(fade_duration); - else - this.FadeOut(fade_duration); - } - } } } From 5e871f9838b16f5319cfa5886dc4a81c93137641 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 15:36:47 +0900 Subject: [PATCH 47/52] Make room playlist use the common rearrangeable design --- .../TestSceneDrawableRoomPlaylist.cs | 16 +- .../Screens/Multi/DrawableRoomPlaylist.cs | 10 +- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 263 +++++++----------- 3 files changed, 112 insertions(+), 177 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index b906ce82e9..393fd281d4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -17,6 +19,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneDrawableRoomPlaylist : OsuTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableRoomPlaylist), + typeof(DrawableRoomPlaylistItem) + }; + [Resolved] private BeatmapManager beatmapManager { get; set; } @@ -26,25 +34,25 @@ namespace osu.Game.Tests.Visual.Multiplayer private DrawableRoomPlaylist playlist; [Test] - public void TestItemsCanNotBeRemovedOrSelectedFromNonEditableAndNonSelectablePlaylist() + public void TestNonEditableNonSelectable() { createPlaylist(false, false); } [Test] - public void TestItemsCanBeRemovedFromEditablePlaylist() + public void TestEditable() { createPlaylist(true, false); } [Test] - public void TestItemsCanBeSelectedInSelectablePlaylist() + public void TestSelectable() { createPlaylist(false, true); } [Test] - public void TestItemsCanBeSelectedAndRemovedFromEditableAndSelectablePlaylist() + public void TestEditableSelectable() { createPlaylist(true, true); } diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs index b42a8575ec..021a8ca176 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Screens.Multi { - public class DrawableRoomPlaylist : RearrangeableListContainer + public class DrawableRoomPlaylist : OsuRearrangeableListContainer { public readonly Bindable SelectedItem = new Bindable(); @@ -45,10 +45,10 @@ namespace osu.Game.Screens.Multi }; } - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer + protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { - ScrollbarVisible = false - }; + d.ScrollbarVisible = false; + }); protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Multi Spacing = new Vector2(0, 2) }; - protected override RearrangeableListItem CreateDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection) + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection) { RequestSelection = requestSelection, RequestDeletion = requestDeletion diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index d2788a5c4b..2bc593d779 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -28,7 +29,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Multi { - public class DrawableRoomPlaylistItem : RearrangeableListItem + public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { public Action RequestSelection; public Action RequestDeletion; @@ -37,7 +38,6 @@ namespace osu.Game.Screens.Multi private Container difficultyIconContainer; private LinkFlowContainer beatmapText; private LinkFlowContainer authorText; - private ItemHandle handle; private ModDisplay modDisplay; private readonly Bindable beatmap = new Bindable(); @@ -55,9 +55,6 @@ namespace osu.Game.Screens.Multi this.allowEdit = allowEdit; this.allowSelection = allowSelection; - RelativeSizeAxes = Axes.X; - Height = 50; - beatmap.BindTo(item.Beatmap); ruleset.BindTo(item.Ruleset); requiredMods.BindTo(item.RequiredMods); @@ -66,118 +63,10 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Alpha = allowEdit ? 1 : 0, - Child = handle = new ItemHandle - { - Size = new Vector2(12), - AlwaysPresent = true, - Alpha = 0, - }, - }, - maskingContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - BorderColour = colours.Yellow, - Children = new Drawable[] - { - new Box // A transparent box that forces the border to be drawn if the panel background is opaque - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - new PanelBackground - { - RelativeSizeAxes = Axes.Both, - Beatmap = { BindTarget = beatmap } - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 8 }, - Spacing = new Vector2(8, 0), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - difficultyIconContainer = new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - beatmapText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, - modDisplay = new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.4f), - DisplayUnrankedText = false, - ExpansionMode = ExpansionMode.AlwaysExpanded - } - } - } - } - } - } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - X = -18, - Children = new Drawable[] - { - new IconButton - { - Icon = FontAwesome.Solid.MinusSquare, - Alpha = allowEdit ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Model), - }, - new PanelDownloadButton(item.Beatmap.Value.BeatmapSet) - { - Size = new Vector2(50, 30), - Alpha = allowEdit ? 0 : 1 - } - } - } - } - } - }, - }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - }; + if (!allowEdit) + HandleColour = HandleColour.Opacity(0); + + maskingContainer.BorderColour = colours.Yellow; } protected override void LoadComplete() @@ -193,6 +82,95 @@ namespace osu.Game.Screens.Multi refresh(); } + protected override Drawable CreateContent() => maskingContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = 50, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box // A transparent box that forces the border to be drawn if the panel background is opaque + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new PanelBackground + { + RelativeSizeAxes = Axes.Both, + Beatmap = { BindTarget = beatmap } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 8 }, + Spacing = new Vector2(8, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + difficultyIconContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + beatmapText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + authorText = new LinkFlowContainer { AutoSizeAxes = Axes.Both }, + modDisplay = new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.4f), + DisplayUnrankedText = false, + ExpansionMode = ExpansionMode.AlwaysExpanded + } + } + } + } + } + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + X = -18, + Children = new Drawable[] + { + new IconButton + { + Icon = FontAwesome.Solid.MinusSquare, + Alpha = allowEdit ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Model), + }, + new PanelDownloadButton(item.Beatmap.Value.BeatmapSet) + { + Size = new Vector2(50, 30), + Alpha = allowEdit ? 0 : 1 + } + } + } + } + }; + private ScheduledDelegate scheduledRefresh; private void scheduleRefresh() @@ -219,16 +197,6 @@ namespace osu.Game.Screens.Multi modDisplay.Current.Value = requiredMods.ToArray(); } - protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; - - protected override bool OnHover(HoverEvent e) - { - handle.UpdateHoverState(true); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false); - protected override bool OnClick(ClickEvent e) { if (allowSelection) @@ -302,46 +270,5 @@ namespace osu.Game.Screens.Multi }; } } - - private class ItemHandle : SpriteIcon - { - public bool HandlingDrag { get; private set; } - private bool isHovering; - - public ItemHandle() - { - Margin = new MarginPadding { Horizontal = 5 }; - - Icon = FontAwesome.Solid.Bars; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - base.OnMouseDown(e); - - HandlingDrag = true; - UpdateHoverState(isHovering); - - return false; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - base.OnMouseUp(e); - - HandlingDrag = false; - UpdateHoverState(isHovering); - } - - public void UpdateHoverState(bool hovering) - { - isHovering = hovering; - - if (isHovering || HandlingDrag) - this.FadeIn(100); - else - this.FadeOut(100); - } - } } } From 1909ea2bd3a17cd3d6a877dd4b01c1585d4f631c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 16:09:15 +0900 Subject: [PATCH 48/52] Add a way to hide the drag handle --- .../Containers/OsuRearrangeableListItem.cs | 17 +++++++++++++++-- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index f75a3330e2..29553954fe 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -23,6 +23,9 @@ namespace osu.Game.Graphics.Containers private Color4 handleColour = Color4.White; + /// + /// The colour of the drag handle. + /// protected Color4 HandleColour { get => handleColour; @@ -38,6 +41,11 @@ namespace osu.Game.Graphics.Containers } } + /// + /// Whether the drag handle should be shown. + /// + protected virtual bool ShowDragHandle => true; + private PlaylistItemHandle handle; protected OsuRearrangeableListItem(TModel item) @@ -50,6 +58,8 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load() { + Container handleContainer; + InternalChild = new GridContainer { RelativeSizeAxes = Axes.X, @@ -58,7 +68,7 @@ namespace osu.Game.Graphics.Containers { new[] { - new Container + handleContainer = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -78,6 +88,9 @@ namespace osu.Game.Graphics.Containers ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }; + + if (!ShowDragHandle) + handleContainer.Alpha = 0; } protected override bool OnDragStart(DragStartEvent e) @@ -107,7 +120,7 @@ namespace osu.Game.Graphics.Containers protected abstract Drawable CreateContent(); - private class PlaylistItemHandle : SpriteIcon + public class PlaylistItemHandle : SpriteIcon { public bool HandlingDrag { get; private set; } private bool isHovering; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 2bc593d779..7156839dfc 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -34,6 +34,8 @@ namespace osu.Game.Screens.Multi public Action RequestSelection; public Action RequestDeletion; + protected override bool ShowDragHandle => allowEdit; + private Container maskingContainer; private Container difficultyIconContainer; private LinkFlowContainer beatmapText; From d6768bba628c2feafac4c28b5719cc83ed83e48a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 16:09:30 +0900 Subject: [PATCH 49/52] Make test playlist items unique --- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 393fd281d4..d43bf9edea 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -79,6 +79,7 @@ namespace osu.Game.Tests.Visual.Multiplayer playlist.Items.Add(new PlaylistItem { + ID = i, Beatmap = { Value = beatmap }, Ruleset = { Value = rulesets[RNG.Next(0, rulesets.Count)] }, RequiredMods = From afd3e4604b5649e4f27e52afa6869e64249e8e2a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 16:48:23 +0900 Subject: [PATCH 50/52] Fix race condition causing selection to be reset incorrectly --- osu.Game/Screens/Multi/DrawableRoomPlaylist.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs index 021a8ca176..01f173db4e 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylist.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.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -38,11 +37,11 @@ namespace osu.Game.Screens.Multi ((DrawableRoomPlaylistItem)newItem).Select(); }, true); - Items.ItemsRemoved += items => + Items.ItemsRemoved += items => Schedule(() => { - if (items.Any(i => i == SelectedItem.Value)) + if (!Items.Contains(SelectedItem.Value)) SelectedItem.Value = null; - }; + }); } protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => From 6a466ea2f584e8c8c74ed8e92414ea00c5532dce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 16:48:30 +0900 Subject: [PATCH 51/52] Improve test cases --- .../TestSceneDrawableRoomPlaylist.cs | 148 +++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index d43bf9edea..267496bba2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -7,17 +7,22 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneDrawableRoomPlaylist : OsuTestScene + public class TestSceneDrawableRoomPlaylist : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -37,26 +42,167 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestNonEditableNonSelectable() { createPlaylist(false, false); + + moveToItem(0); + assertHandleVisibility(0, false); + assertDeleteButtonVisibility(0, false); } [Test] public void TestEditable() { createPlaylist(true, false); + + moveToItem(0); + assertHandleVisibility(0, true); + assertDeleteButtonVisibility(0, true); } [Test] public void TestSelectable() { createPlaylist(false, true); + + moveToItem(0); + assertHandleVisibility(0, false); + assertDeleteButtonVisibility(0, false); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); } [Test] public void TestEditableSelectable() { createPlaylist(true, true); + + moveToItem(0); + assertHandleVisibility(0, true); + assertDeleteButtonVisibility(0, true); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); } + [Test] + public void TestSelectionNotLostAfterRearrangement() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDragger(0); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveToDragger(1, new Vector2(0, 5)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddAssert("item 1 is selected", () => playlist.SelectedItem.Value == playlist.Items[1]); + } + + [Test] + public void TestItemRemovedOnDeletion() + { + PlaylistItem selectedItem = null; + + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); + } + + [Test] + public void TestNextItemSelectedAfterDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + [Test] + public void TestLastItemSelectedAfterLastItemDeleted() + { + createPlaylist(true, true); + + AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. + AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); + + moveToItem(19); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(19); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]); + } + + [Test] + public void TestSelectionResetWhenAllItemsDeleted() + { + createPlaylist(true, true); + + AddStep("remove all but one item", () => + { + playlist.Items.RemoveRange(1, playlist.Items.Count - 1); + }); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("selected item is null", () => playlist.SelectedItem.Value == null); + } + + // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) + // [Test] + public void TestNextItemSelectedAfterExternalDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("remove item 0", () => playlist.Items.RemoveAt(0)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + private void moveToItem(int index, Vector2? offset = null) + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType>().ElementAt(index), offset)); + + private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () => + { + var item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType.PlaylistItemHandle>().Single(), offset); + }); + + private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => + { + var item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + }); + + private void assertHandleVisibility(int index, bool visible) + => AddAssert($"handle {index} {(visible ? "is" : "is not")} visible", + () => (playlist.ChildrenOfType.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible); + + private void assertDeleteButtonVisibility(int index, bool visible) + => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + private void createPlaylist(bool allowEdit, bool allowSelection) => AddStep("create playlist", () => { Child = playlist = new DrawableRoomPlaylist(allowEdit, allowSelection) From aceba8791c2e3bbbb1a541351257900ae3a56fa0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 14 Feb 2020 16:55:05 +0900 Subject: [PATCH 52/52] Clean up selection handling --- .../TestSceneDrawableRoomPlaylist.cs | 8 ++- .../Screens/Multi/DrawableRoomPlaylist.cs | 12 +--- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 63 +++++++++---------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 267496bba2..c93d59acdf 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -46,6 +46,9 @@ namespace osu.Game.Tests.Visual.Multiplayer moveToItem(0); assertHandleVisibility(0, false); assertDeleteButtonVisibility(0, false); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddAssert("no item selected", () => playlist.SelectedItem.Value == null); } [Test] @@ -56,6 +59,9 @@ namespace osu.Game.Tests.Visual.Multiplayer moveToItem(0); assertHandleVisibility(0, true); assertDeleteButtonVisibility(0, true); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddAssert("no item selected", () => playlist.SelectedItem.Value == null); } [Test] @@ -165,7 +171,7 @@ namespace osu.Game.Tests.Visual.Multiplayer moveToDeleteButton(0); AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - AddAssert("selected item is null", () => playlist.SelectedItem.Value == null); + AddAssert("no item selected", () => playlist.SelectedItem.Value == null); } // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs index 01f173db4e..b139c61166 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs @@ -28,15 +28,7 @@ namespace osu.Game.Screens.Multi { base.LoadComplete(); - SelectedItem.BindValueChanged(item => - { - if (item.OldValue != null && ItemMap.TryGetValue(item.OldValue, out var oldItem)) - ((DrawableRoomPlaylistItem)oldItem).Deselect(); - - if (item.NewValue != null && ItemMap.TryGetValue(item.NewValue, out var newItem)) - ((DrawableRoomPlaylistItem)newItem).Select(); - }, true); - + // Scheduled since items are removed and re-added upon rearrangement Items.ItemsRemoved += items => Schedule(() => { if (!Items.Contains(SelectedItem.Value)) @@ -58,7 +50,7 @@ namespace osu.Game.Screens.Multi protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection) { - RequestSelection = requestSelection, + SelectedItem = { BindTarget = SelectedItem }, RequestDeletion = requestDeletion }; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 7156839dfc..b1e69e4c4f 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -31,9 +31,10 @@ namespace osu.Game.Screens.Multi { public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { - public Action RequestSelection; public Action RequestDeletion; + public readonly Bindable SelectedItem = new Bindable(); + protected override bool ShowDragHandle => allowEdit; private Container maskingContainer; @@ -75,6 +76,8 @@ namespace osu.Game.Screens.Multi { base.LoadComplete(); + SelectedItem.BindValueChanged(selected => maskingContainer.BorderThickness = selected.NewValue == Model ? 5 : 0); + beatmap.BindValueChanged(_ => scheduleRefresh()); ruleset.BindValueChanged(_ => scheduleRefresh()); @@ -84,6 +87,32 @@ namespace osu.Game.Screens.Multi refresh(); } + private ScheduledDelegate scheduledRefresh; + + private void scheduleRefresh() + { + scheduledRefresh?.Cancel(); + scheduledRefresh = Schedule(refresh); + } + + private void refresh() + { + difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) }; + + beatmapText.Clear(); + beatmapText.AddLink(item.Beatmap.ToString(), LinkAction.OpenBeatmap, item.Beatmap.Value.OnlineBeatmapID.ToString()); + + authorText.Clear(); + + if (item.Beatmap?.Value?.Metadata?.Author != null) + { + authorText.AddText("mapped by "); + authorText.AddUserLink(item.Beatmap.Value?.Metadata.Author); + } + + modDisplay.Current.Value = requiredMods.ToArray(); + } + protected override Drawable CreateContent() => maskingContainer = new Container { RelativeSizeAxes = Axes.X, @@ -173,43 +202,13 @@ namespace osu.Game.Screens.Multi } }; - private ScheduledDelegate scheduledRefresh; - - private void scheduleRefresh() - { - scheduledRefresh?.Cancel(); - scheduledRefresh = Schedule(refresh); - } - - private void refresh() - { - difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) }; - - beatmapText.Clear(); - beatmapText.AddLink(item.Beatmap.ToString(), LinkAction.OpenBeatmap, item.Beatmap.Value.OnlineBeatmapID.ToString()); - - authorText.Clear(); - - if (item.Beatmap?.Value?.Metadata?.Author != null) - { - authorText.AddText("mapped by "); - authorText.AddUserLink(item.Beatmap.Value?.Metadata.Author); - } - - modDisplay.Current.Value = requiredMods.ToArray(); - } - protected override bool OnClick(ClickEvent e) { if (allowSelection) - RequestSelection?.Invoke(Model); + SelectedItem.Value = Model; return true; } - public void Select() => maskingContainer.BorderThickness = 5; - - public void Deselect() => maskingContainer.BorderThickness = 0; - // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) {