From 16ffedde8a8ac640e61f98c0949324c699a5b05f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:17:57 +0300 Subject: [PATCH 01/26] Add year parameter to GetNewsRequest --- osu.Game/Online/API/Requests/GetNewsRequest.cs | 8 +++++++- osu.Game/Overlays/News/Displays/FrontPageDisplay.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index 36d9dc0652..3eb29f1292 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -8,10 +8,12 @@ namespace osu.Game.Online.API.Requests { public class GetNewsRequest : APIRequest { + private readonly int year; private readonly Cursor cursor; - public GetNewsRequest(Cursor cursor = null) + public GetNewsRequest(int year = 0, Cursor cursor = null) { + this.year = year; this.cursor = cursor; } @@ -19,6 +21,10 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); req.AddCursor(cursor); + + if (year != 0) + req.AddParameter("year", year.ToString()); + return req; } diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index a1bc6c650b..45e11a6f15 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.News.Displays { request?.Cancel(); - request = new GetNewsRequest(lastCursor); + request = new GetNewsRequest(cursor: lastCursor); request.Success += response => Schedule(() => onSuccess(response)); api.PerformAsync(request); } From 150ed01c627e49e00a75f8c7b3a8a68e0bfea424 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:22:55 +0300 Subject: [PATCH 02/26] Make NewsSidebar scrollable --- osu.Game/Overlays/News/Sidebar/NewsSidebar.cs | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs index d14ad90ef4..9e397e78c8 100644 --- a/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs +++ b/osu.Game/Overlays/News/Sidebar/NewsSidebar.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Shapes; using osuTK; using System.Linq; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.News.Sidebar { @@ -31,30 +32,55 @@ namespace osu.Game.Overlays.News.Sidebar RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background4 }, + new Box + { + RelativeSizeAxes = Axes.Y, + Width = OsuScrollContainer.SCROLL_BAR_HEIGHT, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colourProvider.Background3, + Alpha = 0.5f + }, new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin + Child = new OsuScrollContainer { - Vertical = 20, - Left = 50, - Right = 30 - }, - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 20), - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + Child = new Container { - new YearsPanel(), - monthsFlow = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 3 }, // Addeded 3px back + Child = new Container { - AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10) + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Vertical = 20, + Left = 50, + Right = 30 + }, + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new YearsPanel(), + monthsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10) + } + } + } } } } From 6cc4ffadabda1eb13d8b8d96a7a7d311f9314721 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:28:12 +0300 Subject: [PATCH 03/26] Implement sticky container for sidebar in NewsOverlay --- osu.Game/Overlays/NewsOverlay.cs | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 5beb285216..ddd328c860 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -1,11 +1,14 @@ // 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.Threading; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Overlays.News; using osu.Game.Overlays.News.Displays; +using osu.Game.Overlays.News.Sidebar; namespace osu.Game.Overlays { @@ -13,9 +16,44 @@ namespace osu.Game.Overlays { private readonly Bindable article = new Bindable(null); + protected override Container Content => content; + + private readonly Container content; + private readonly Container sidebarContainer; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { + base.Content.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + sidebarContainer = new Container + { + AutoSizeAxes = Axes.X, + Child = new NewsSidebar() + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }); } protected override void LoadComplete() @@ -90,6 +128,14 @@ namespace osu.Game.Overlays }, (cancellationToken = new CancellationTokenSource()).Token); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + sidebarContainer.Height = DrawHeight; + sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); + } + protected override void Dispose(bool isDisposing) { cancellationToken?.Cancel(); From e3ed9b8135b93c3b14685de298f1a6e25ec44f6b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:36:05 +0300 Subject: [PATCH 04/26] Implement sidebar metadata handling in NewsOverlay --- .../News/Displays/FrontPageDisplay.cs | 20 ++++++++- osu.Game/Overlays/NewsOverlay.cs | 44 ++++++++++++++++--- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs index 45e11a6f15..7691e4a901 100644 --- a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs +++ b/osu.Game/Overlays/News/Displays/FrontPageDisplay.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 System.Threading; using osu.Framework.Allocation; @@ -15,6 +16,8 @@ namespace osu.Game.Overlays.News.Displays { public class FrontPageDisplay : CompositeDrawable { + public Action ResponseReceived; + [Resolved] private IAPIProvider api { get; set; } @@ -24,6 +27,13 @@ namespace osu.Game.Overlays.News.Displays private GetNewsRequest request; private Cursor lastCursor; + private readonly int year; + + public FrontPageDisplay(int year = 0) + { + this.year = year; + } + [BackgroundDependencyLoader] private void load() { @@ -74,13 +84,15 @@ namespace osu.Game.Overlays.News.Displays { request?.Cancel(); - request = new GetNewsRequest(cursor: lastCursor); + request = new GetNewsRequest(year, lastCursor); request.Success += response => Schedule(() => onSuccess(response)); api.PerformAsync(request); } private CancellationTokenSource cancellationToken; + private bool initialLoad = true; + private void onSuccess(GetNewsResponse response) { cancellationToken?.Cancel(); @@ -101,6 +113,12 @@ namespace osu.Game.Overlays.News.Displays content.Add(loaded); showMore.IsLoading = false; showMore.Alpha = lastCursor == null ? 0 : 1; + + if (initialLoad) + { + ResponseReceived?.Invoke(response); + initialLoad = false; + } }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index ddd328c860..d3f407fc0f 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -20,6 +20,7 @@ namespace osu.Game.Overlays private readonly Container content; private readonly Container sidebarContainer; + private readonly NewsSidebar sidebar; public NewsOverlay() : base(OverlayColourScheme.Purple, false) @@ -44,7 +45,7 @@ namespace osu.Game.Overlays sidebarContainer = new Container { AutoSizeAxes = Axes.X, - Child = new NewsSidebar() + Child = sidebar = new NewsSidebar() }, content = new Container { @@ -94,6 +95,12 @@ namespace osu.Game.Overlays Show(); } + public void ShowYear(int year) + { + showYear(year); + Show(); + } + public void ShowArticle(string slug) { article.Value = slug; @@ -102,6 +109,14 @@ namespace osu.Game.Overlays private CancellationTokenSource cancellationToken; + private void showYear(int year) + { + cancellationToken?.Cancel(); + Loading.Show(); + + loadFrontPage(year); + } + private void onArticleChanged(ValueChangedEvent e) { cancellationToken?.Cancel(); @@ -109,13 +124,33 @@ namespace osu.Game.Overlays if (e.NewValue == null) { - Header.SetFrontPage(); - LoadDisplay(new FrontPageDisplay()); + loadFrontPage(); return; } - Header.SetArticle(e.NewValue); + loadArticle(e.NewValue); + } + + private void loadFrontPage(int year = 0) + { + Header.SetFrontPage(); + + var page = new FrontPageDisplay(year); + page.ResponseReceived += r => + { + sidebar.Metadata.Value = r.SidebarMetadata; + Loading.Hide(); + }; + LoadDisplay(page); + } + + private void loadArticle(string article) + { + Header.SetArticle(article); + + // Temporary, should be handled by ArticleDisplay later LoadDisplay(Empty()); + Loading.Hide(); } protected void LoadDisplay(Drawable display) @@ -124,7 +159,6 @@ namespace osu.Game.Overlays LoadComponentAsync(display, loaded => { Child = loaded; - Loading.Hide(); }, (cancellationToken = new CancellationTokenSource()).Token); } From d60478851f8bf9a63aa158b29b9a3a3d784451c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 19 May 2021 15:38:53 +0300 Subject: [PATCH 05/26] Add proper action to YearButton --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index b6bbdbb6d4..232b995cd6 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -81,6 +81,9 @@ namespace osu.Game.Overlays.News.Sidebar { public int Year { get; } + [Resolved(canBeNull: true)] + private NewsOverlay overlay { get; set; } + private readonly bool isCurrent; public YearButton(int year, bool isCurrent) @@ -106,7 +109,7 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = isCurrent ? Color4.White : colourProvider.Light2; HoverColour = isCurrent ? Color4.White : colourProvider.Light1; - Action = () => { }; // Avoid button being disabled since there's no proper action assigned. + Action = () => overlay?.ShowYear(Year); } } } From 713f69ea5505d21359a34c3d9bb3c8980580709b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:11:42 +0900 Subject: [PATCH 06/26] Tidy up load process --- osu.Game/Overlays/NewsOverlay.cs | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index d3f407fc0f..f7294dd880 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; + private CancellationTokenSource cancellationToken; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { @@ -97,7 +99,7 @@ namespace osu.Game.Overlays public void ShowYear(int year) { - showYear(year); + loadFrontPage(year); Show(); } @@ -107,32 +109,18 @@ namespace osu.Game.Overlays Show(); } - private CancellationTokenSource cancellationToken; - - private void showYear(int year) - { - cancellationToken?.Cancel(); - Loading.Show(); - - loadFrontPage(year); - } - private void onArticleChanged(ValueChangedEvent e) { - cancellationToken?.Cancel(); - Loading.Show(); - if (e.NewValue == null) - { loadFrontPage(); - return; - } - - loadArticle(e.NewValue); + else + loadArticle(e.NewValue); } private void loadFrontPage(int year = 0) { + beginLoading(); + Header.SetFrontPage(); var page = new FrontPageDisplay(year); @@ -146,6 +134,8 @@ namespace osu.Game.Overlays private void loadArticle(string article) { + beginLoading(); + Header.SetArticle(article); // Temporary, should be handled by ArticleDisplay later @@ -153,6 +143,12 @@ namespace osu.Game.Overlays Loading.Hide(); } + private void beginLoading() + { + cancellationToken?.Cancel(); + Loading.Show(); + } + protected void LoadDisplay(Drawable display) { ScrollFlow.ScrollToStart(); From ac8efdeabdceac44fd261febeae971bb2deaa087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:12:34 +0900 Subject: [PATCH 07/26] Move private methods down --- osu.Game/Overlays/NewsOverlay.cs | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index f7294dd880..280e224255 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays private CancellationTokenSource cancellationToken; + private bool displayUpdateRequired = true; + public NewsOverlay() : base(OverlayColourScheme.Purple, false) { @@ -72,8 +74,6 @@ namespace osu.Game.Overlays ShowFrontPage = ShowFrontPage }; - private bool displayUpdateRequired = true; - protected override void PopIn() { base.PopIn(); @@ -109,6 +109,23 @@ namespace osu.Game.Overlays Show(); } + protected void LoadDisplay(Drawable display) + { + ScrollFlow.ScrollToStart(); + LoadComponentAsync(display, loaded => + { + Child = loaded; + }, (cancellationToken = new CancellationTokenSource()).Token); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + sidebarContainer.Height = DrawHeight; + sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); + } + private void onArticleChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -149,23 +166,6 @@ namespace osu.Game.Overlays Loading.Show(); } - protected void LoadDisplay(Drawable display) - { - ScrollFlow.ScrollToStart(); - LoadComponentAsync(display, loaded => - { - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - sidebarContainer.Height = DrawHeight; - sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); - } - protected override void Dispose(bool isDisposing) { cancellationToken?.Cancel(); From 673ca4c2a11252b24219222dd9f5f971fc523102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 14:30:40 +0900 Subject: [PATCH 08/26] Tidy up content container specification --- osu.Game/Overlays/NewsOverlay.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 280e224255..b082614a6e 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -16,12 +16,11 @@ namespace osu.Game.Overlays { private readonly Bindable article = new Bindable(null); - protected override Container Content => content; - - private readonly Container content; private readonly Container sidebarContainer; private readonly NewsSidebar sidebar; + private readonly Container content; + private CancellationTokenSource cancellationToken; private bool displayUpdateRequired = true; @@ -29,7 +28,7 @@ namespace osu.Game.Overlays public NewsOverlay() : base(OverlayColourScheme.Purple, false) { - base.Content.Add(new GridContainer + Child = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -58,7 +57,7 @@ namespace osu.Game.Overlays } } } - }); + }; } protected override void LoadComplete() @@ -112,16 +111,12 @@ namespace osu.Game.Overlays protected void LoadDisplay(Drawable display) { ScrollFlow.ScrollToStart(); - LoadComponentAsync(display, loaded => - { - Child = loaded; - }, (cancellationToken = new CancellationTokenSource()).Token); + LoadComponentAsync(display, loaded => content.Child = loaded, (cancellationToken = new CancellationTokenSource()).Token); } protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - sidebarContainer.Height = DrawHeight; sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); } From 489caebf5996f1ca676e7d1700e44da8fd21110e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:15:19 +0900 Subject: [PATCH 09/26] Move bind `LoadComplete` code out of constructor --- osu.Game/Overlays/News/NewsHeader.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index 63174128e7..94bfd62c32 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -19,13 +19,18 @@ namespace osu.Game.Overlays.News { TabControl.AddItem(front_page_string); + article.BindValueChanged(onArticleChanged, true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(e => { if (e.NewValue == front_page_string) ShowFrontPage?.Invoke(); }); - - article.BindValueChanged(onArticleChanged, true); } public void SetFrontPage() => article.Value = null; From d4530313aa8605f9071d9a40f8e1d0bf192d7014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:15:46 +0900 Subject: [PATCH 10/26] Tidy event parameter naming --- osu.Game/Overlays/NewsOverlay.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index b082614a6e..df564704fa 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -68,10 +68,7 @@ namespace osu.Game.Overlays article.BindValueChanged(onArticleChanged); } - protected override NewsHeader CreateHeader() => new NewsHeader - { - ShowFrontPage = ShowFrontPage - }; + protected override NewsHeader CreateHeader() => new NewsHeader { ShowFrontPage = ShowFrontPage }; protected override void PopIn() { @@ -121,12 +118,12 @@ namespace osu.Game.Overlays sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0)); } - private void onArticleChanged(ValueChangedEvent e) + private void onArticleChanged(ValueChangedEvent article) { - if (e.NewValue == null) + if (article.NewValue == null) loadFrontPage(); else - loadArticle(e.NewValue); + loadArticle(article.NewValue); } private void loadFrontPage(int year = 0) From 9267d23dc282976abbacebd82281da2ba71cfd3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:23:49 +0900 Subject: [PATCH 11/26] Make year nullable rather than defaulting to zero --- osu.Game/Online/API/Requests/GetNewsRequest.cs | 8 ++++---- .../Displays/{FrontPageDisplay.cs => ArticleListing.cs} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/News/Displays/{FrontPageDisplay.cs => ArticleListing.cs} (100%) diff --git a/osu.Game/Online/API/Requests/GetNewsRequest.cs b/osu.Game/Online/API/Requests/GetNewsRequest.cs index 3eb29f1292..992ccc6d59 100644 --- a/osu.Game/Online/API/Requests/GetNewsRequest.cs +++ b/osu.Game/Online/API/Requests/GetNewsRequest.cs @@ -8,10 +8,10 @@ namespace osu.Game.Online.API.Requests { public class GetNewsRequest : APIRequest { - private readonly int year; + private readonly int? year; private readonly Cursor cursor; - public GetNewsRequest(int year = 0, Cursor cursor = null) + public GetNewsRequest(int? year = null, Cursor cursor = null) { this.year = year; this.cursor = cursor; @@ -22,8 +22,8 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddCursor(cursor); - if (year != 0) - req.AddParameter("year", year.ToString()); + if (year.HasValue) + req.AddParameter("year", year.Value.ToString()); return req; } diff --git a/osu.Game/Overlays/News/Displays/FrontPageDisplay.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs similarity index 100% rename from osu.Game/Overlays/News/Displays/FrontPageDisplay.cs rename to osu.Game/Overlays/News/Displays/ArticleListing.cs From 958d51141da905b123f6167a0a60d506862b5f86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 15:24:01 +0900 Subject: [PATCH 12/26] Rename `FrontPageDisplay` to `ArticleListing` --- osu.Game/Overlays/News/Displays/ArticleListing.cs | 13 ++++++++++--- osu.Game/Overlays/NewsOverlay.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index 7691e4a901..e713b3de84 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -14,7 +14,10 @@ using osuTK; namespace osu.Game.Overlays.News.Displays { - public class FrontPageDisplay : CompositeDrawable + /// + /// Lists articles in a vertical flow for a specified year. + /// + public class ArticleListing : CompositeDrawable { public Action ResponseReceived; @@ -27,9 +30,13 @@ namespace osu.Game.Overlays.News.Displays private GetNewsRequest request; private Cursor lastCursor; - private readonly int year; + private readonly int? year; - public FrontPageDisplay(int year = 0) + /// + /// Instantiate a listing for the specified year. + /// + /// The year to load articles from. If null, will show the most recent articles. + public ArticleListing(int? year = null) { this.year = year; } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index df564704fa..510cdba020 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays Header.SetFrontPage(); - var page = new FrontPageDisplay(year); + var page = new ArticleListing(year); page.ResponseReceived += r => { sidebar.Metadata.Value = r.SidebarMetadata; From df5970fab4585e6649a2cec85f7d38e5ee47b264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 19:34:53 +0900 Subject: [PATCH 13/26] Create base implementations of the two most common `TernaryStateMenuItem`s --- .../Components/PathControlPointVisualiser.cs | 12 +--- .../Edit/TaikoSelectionHandler.cs | 4 +- .../TestSceneStatefulMenuItem.cs | 56 +++++++++++++++++-- .../UserInterface/TernaryStateMenuItem.cs | 37 ++---------- .../TernaryStateRadioMenuItem.cs | 23 ++++++++ .../TernaryStateToggleMenuItem.cs | 42 ++++++++++++++ .../Components/EditorSelectionHandler.cs | 4 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Skinning/Editor/SkinSelectionHandler.cs | 14 +---- 9 files changed, 129 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs create mode 100644 osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 44c3056910..c36768baba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components int totalCount = Pieces.Count(p => p.IsSelected.Value); int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type); - var item = new PathTypeMenuItem(type, () => + var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ => { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); @@ -258,15 +258,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return item; } - - private class PathTypeMenuItem : TernaryStateMenuItem - { - public PathTypeMenuItem(PathType? type, Action action) - : base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke()) - { - } - - private static TernaryState changeState(TernaryState state) => TernaryState.True; - } } } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 48ee0d4cf4..a24130d6ac 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -76,10 +76,10 @@ namespace osu.Game.Rulesets.Taiko.Edit protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable> selection) { if (selection.All(s => s.Item is Hit)) - yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } }; + yield return new TernaryStateToggleMenuItem("Rim") { State = { BindTarget = selectionRimState } }; if (selection.All(s => s.Item is TaikoHitObject)) - yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; + yield return new TernaryStateToggleMenuItem("Strong") { State = { BindTarget = selectionStrongState } }; foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs index 29aeb6a4b2..18ec631f37 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStatefulMenuItem.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene { [Test] - public void TestTernaryMenuItem() + public void TestTernaryRadioMenuItem() { OsuMenu menu = null; @@ -30,9 +30,57 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Items = new[] { - new TernaryStateMenuItem("First"), - new TernaryStateMenuItem("Second") { State = { BindTarget = state } }, - new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } }, + new TernaryStateRadioMenuItem("First"), + new TernaryStateRadioMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateRadioMenuItem("Third") { State = { Value = TernaryState.True } }, + } + }; + }); + + checkState(TernaryState.Indeterminate); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.True); + + click(); + checkState(TernaryState.True); + + AddStep("change state via bindable", () => state.Value = TernaryState.True); + + void click() => + AddStep("click", () => + { + InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); + }); + + void checkState(TernaryState expected) + => AddAssert($"state is {expected}", () => state.Value == expected); + } + + [Test] + public void TestTernaryToggleMenuItem() + { + OsuMenu menu = null; + + Bindable state = new Bindable(TernaryState.Indeterminate); + + AddStep("create menu", () => + { + state.Value = TernaryState.Indeterminate; + + Child = menu = new OsuMenu(Direction.Vertical, true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Items = new[] + { + new TernaryStateToggleMenuItem("First"), + new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } }, + new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } }, } }; }); diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index acf4065f49..5c623150b7 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -9,28 +9,17 @@ namespace osu.Game.Graphics.UserInterface /// /// An with three possible states. /// - public class TernaryStateMenuItem : StatefulMenuItem + public abstract class TernaryStateMenuItem : StatefulMenuItem { /// /// Creates a new . /// /// The text to display. + /// A function to inform what the next state should be when this item is clicked. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) - : this(text, getNextState, type, action) - { - } - - /// - /// Creates a new . - /// - /// The text to display. - /// A function that mutates a state to another state after this is pressed. - /// The type of action which this performs. - /// A delegate to be invoked when this is pressed. - protected TernaryStateMenuItem(string text, Func changeStateFunc, MenuItemType type, Action action) - : base(text, changeStateFunc, type, action) + protected TernaryStateMenuItem(string text, Func nextStateFunction, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, nextStateFunction, type, action) { } @@ -47,23 +36,5 @@ namespace osu.Game.Graphics.UserInterface return null; } - - private static TernaryState getNextState(TernaryState state) - { - switch (state) - { - case TernaryState.False: - return TernaryState.True; - - case TernaryState.Indeterminate: - return TernaryState.True; - - case TernaryState.True: - return TernaryState.False; - - default: - throw new ArgumentOutOfRangeException(nameof(state), state, null); - } - } } } diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs new file mode 100644 index 0000000000..aa83b0567b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + public class TernaryStateRadioMenuItem : TernaryStateMenuItem + { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + public TernaryStateRadioMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, getNextState, type, action) + { + } + + private static TernaryState getNextState(TernaryState state) => TernaryState.True; + } +} diff --git a/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs new file mode 100644 index 0000000000..ce951984fd --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TernaryStateToggleMenuItem.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A ternary state menu item which toggles the state of this item false if clicked when true. + /// + public class TernaryStateToggleMenuItem : TernaryStateMenuItem + { + /// + /// Creates a new . + /// + /// The text to display. + /// The type of action which this performs. + /// A delegate to be invoked when this is pressed. + public TernaryStateToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) + : base(text, getNextState, type, action) + { + } + + private static TernaryState getNextState(TernaryState state) + { + switch (state) + { + case TernaryState.False: + return TernaryState.True; + + case TernaryState.Indeterminate: + return TernaryState.True; + + case TernaryState.True: + return TernaryState.False; + + default: + throw new ArgumentOutOfRangeException(nameof(state), state, null); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 6ab4ca8267..2141c490df 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -168,13 +168,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (SelectedBlueprints.All(b => b.Item is IHasComboInformation)) { - yield return new TernaryStateMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; + yield return new TernaryStateToggleMenuItem("New combo") { State = { BindTarget = SelectionNewComboState } }; } yield return new OsuMenuItem("Sound") { Items = SelectionSampleStates.Select(kvp => - new TernaryStateMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() + new TernaryStateToggleMenuItem(kvp.Value.Description) { State = { BindTarget = kvp.Value } }).ToArray() }; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index d7e901b71e..a3fca3d4e1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateMenuItem(collection.Name.Value, MenuItemType.Standard, s => + return new TernaryStateToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 2eb4ea107d..2cfb9d0f96 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -100,7 +100,7 @@ namespace osu.Game.Skinning.Editor foreach (var item in base.GetContextMenuItemsForSelection(selection)) yield return item; - IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) + IEnumerable createAnchorItems(Func checkFunction, Action applyFunction) { var displayableAnchors = new[] { @@ -117,7 +117,7 @@ namespace osu.Game.Skinning.Editor return displayableAnchors.Select(a => { - return new AnchorMenuItem(a, selection, _ => applyFunction(a)) + return new TernaryStateRadioMenuItem(a.ToString(), MenuItemType.Standard, _ => applyFunction(a)) { State = { Value = GetStateFromSelection(selection, c => checkFunction((Drawable)c.Item) == a) } }; @@ -166,15 +166,5 @@ namespace osu.Game.Skinning.Editor scale.Y = scale.X; } } - - public class AnchorMenuItem : TernaryStateMenuItem - { - public AnchorMenuItem(Anchor anchor, IEnumerable> selection, Action action) - : base(anchor.ToString(), getNextState, MenuItemType.Standard, action) - { - } - - private static TernaryState getNextState(TernaryState state) => TernaryState.True; - } } } From 1848bd902d11f5053237b8aa15636a96931f3867 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 19:51:07 +0900 Subject: [PATCH 14/26] Fix skin editor context menus not dismissing when clicking away --- osu.Game/Skinning/Editor/SkinEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index f24b0c71c0..07a94cac7a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Skinning.Editor { [Cached(typeof(SkinEditor))] - public class SkinEditor : FocusedOverlayContainer + public class SkinEditor : VisibilityContainer { public const double TRANSITION_DURATION = 500; From 0f4b502fdf8c1f9651d0b81dc6828f8358826d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 20 May 2021 20:09:22 +0900 Subject: [PATCH 15/26] Add missing xmldoc --- osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs index aa83b0567b..46eda06294 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs @@ -5,6 +5,9 @@ using System; namespace osu.Game.Graphics.UserInterface { + /// + /// A ternary state menu item which will always set the item to true on click, even if already true. + /// public class TernaryStateRadioMenuItem : TernaryStateMenuItem { /// From c48b5eebdd7346cbea7b37c727f925f12e173c37 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 May 2021 15:45:39 +0300 Subject: [PATCH 16/26] Don't reload the context when clicking selected year button --- osu.Game/Overlays/News/Sidebar/YearsPanel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs index 232b995cd6..b07c9924b9 100644 --- a/osu.Game/Overlays/News/Sidebar/YearsPanel.cs +++ b/osu.Game/Overlays/News/Sidebar/YearsPanel.cs @@ -109,7 +109,11 @@ namespace osu.Game.Overlays.News.Sidebar { IdleColour = isCurrent ? Color4.White : colourProvider.Light2; HoverColour = isCurrent ? Color4.White : colourProvider.Light1; - Action = () => overlay?.ShowYear(Year); + Action = () => + { + if (!isCurrent) + overlay?.ShowYear(Year); + }; } } } From 40ca94cd7b9e16d32b81b199e7a24f22f191d6ce Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 20 May 2021 16:04:51 +0300 Subject: [PATCH 17/26] Fix incorrect year being passed on first load --- osu.Game/Overlays/NewsOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index 510cdba020..af3fa9c3b0 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays loadArticle(article.NewValue); } - private void loadFrontPage(int year = 0) + private void loadFrontPage(int? year = null) { beginLoading(); From 092d0f9b7678a075011029b3300a07b5d111f70f Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 01:20:18 +0700 Subject: [PATCH 18/26] add breadcrumb header test scene --- .../TestSceneBreadcrumbControlHeader.cs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs new file mode 100644 index 0000000000..1439c65a14 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.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.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneBreadcrumbControlHeader : OsuTestScene + { + private static readonly string[] items = { "first", "second", "third", "fourth", "fifth" }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red); + + private TestHeader header; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = header = new TestHeader + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + [Test] + public void TestAddAndRemoveItem() + { + foreach (var item in items.Skip(1)) + AddStep($"Add {item} item", () => header.AddItem(item)); + + foreach (var item in items.Reverse().SkipLast(3)) + AddStep($"Remove {item} item", () => header.RemoveItem(item)); + + AddStep($"Clear item", () => header.ClearItem()); + + foreach (var item in items) + AddStep($"Add {item} item", () => header.AddItem(item)); + + foreach (var item in items) + AddStep($"Remove {item} item", () => header.RemoveItem(item)); + } + + private class TestHeader : BreadcrumbControlOverlayHeader + { + public TestHeader() + { + TabControl.AddItem(items[0]); + Current.Value = items[0]; + } + + public void AddItem(string value) + { + TabControl.AddItem(value); + Current.Value = TabControl.Items.LastOrDefault(); + } + + public void RemoveItem(string value) + { + TabControl.RemoveItem(value); + Current.Value = TabControl.Items.LastOrDefault(); + } + + public void ClearItem() + { + TabControl.Clear(); + Current.Value = null; + } + + protected override OverlayTitle CreateTitle() => new TestTitle(); + } + + private class TestTitle : OverlayTitle + { + public TestTitle() + { + Title = "Test Title"; + } + } + } +} From 236124496d208c9bcf554ee36ead97170f5705ce Mon Sep 17 00:00:00 2001 From: Gagah Pangeran Rosfatiputra Date: Fri, 21 May 2021 01:20:38 +0700 Subject: [PATCH 19/26] add missing accent colour in control tab item --- osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs index 81315f9638..443b3dcf01 100644 --- a/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs +++ b/osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs @@ -26,7 +26,10 @@ namespace osu.Game.Overlays AccentColour = colourProvider.Light2; } - protected override TabItem CreateTabItem(string value) => new ControlTabItem(value); + protected override TabItem CreateTabItem(string value) => new ControlTabItem(value) + { + AccentColour = AccentColour, + }; private class ControlTabItem : BreadcrumbTabItem { From b521405ec8f06393b2b1d841dd5ea4a7c148ae66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 May 2021 20:46:18 +0200 Subject: [PATCH 20/26] Trim redundant string interpolation --- .../Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs index 1439c65a14..45868b2872 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var item in items.Reverse().SkipLast(3)) AddStep($"Remove {item} item", () => header.RemoveItem(item)); - AddStep($"Clear item", () => header.ClearItem()); + AddStep("Clear item", () => header.ClearItem()); foreach (var item in items) AddStep($"Add {item} item", () => header.AddItem(item)); From f35a07fee729586abf7723a742a146164c866fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 May 2021 20:47:50 +0200 Subject: [PATCH 21/26] Rename method for better comprehension --- .../Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs index 45868b2872..90c3e142df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControlHeader.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface foreach (var item in items.Reverse().SkipLast(3)) AddStep($"Remove {item} item", () => header.RemoveItem(item)); - AddStep("Clear item", () => header.ClearItem()); + AddStep("Clear items", () => header.ClearItems()); foreach (var item in items) AddStep($"Add {item} item", () => header.AddItem(item)); @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.UserInterface Current.Value = TabControl.Items.LastOrDefault(); } - public void ClearItem() + public void ClearItems() { TabControl.Clear(); Current.Value = null; From 895eb14c5ae222877b49cd322409f0ce3736f927 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 14:09:30 +0900 Subject: [PATCH 22/26] Forcefully end playing to fix test failures --- osu.Game/Online/Spectator/SpectatorClient.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index cb98b01bed..3a29e73b8f 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -174,6 +174,9 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { + if (!IsPlaying) + return; + IsPlaying = false; currentBeatmap = null; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 6317a41bec..39f9e2d388 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; +using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Replays; using osu.Game.Rulesets; @@ -93,6 +94,9 @@ namespace osu.Game.Screens.Play [Resolved] private MusicController musicController { get; set; } + [Resolved] + private SpectatorClient spectatorClient { get; set; } + private Sample sampleRestart; public BreakOverlay BreakOverlay; @@ -882,6 +886,11 @@ namespace osu.Game.Screens.Play return true; } + // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. + // To resolve test failures, forcefully end playing synchronously when this screen exits. + // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. + spectatorClient.EndPlaying(); + // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. (GameplayClockContainer as MasterGameplayClockContainer)?.StopUsingBeatmapClock(); From fbe4d7e03c2d9e8aca399adb1077f2bf2a47b8b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 21 May 2021 15:41:31 +0900 Subject: [PATCH 23/26] Improve code quality around cursor and upwards passing of response data --- .../Overlays/News/Displays/ArticleListing.cs | 30 +++++++------------ osu.Game/Overlays/NewsOverlay.cs | 6 ++-- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/News/Displays/ArticleListing.cs b/osu.Game/Overlays/News/Displays/ArticleListing.cs index e713b3de84..b49326a1f1 100644 --- a/osu.Game/Overlays/News/Displays/ArticleListing.cs +++ b/osu.Game/Overlays/News/Displays/ArticleListing.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Overlays.News.Displays @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.News.Displays /// public class ArticleListing : CompositeDrawable { - public Action ResponseReceived; + public Action SidebarMetadataUpdated; [Resolved] private IAPIProvider api { get; set; } @@ -98,34 +99,23 @@ namespace osu.Game.Overlays.News.Displays private CancellationTokenSource cancellationToken; - private bool initialLoad = true; - private void onSuccess(GetNewsResponse response) { cancellationToken?.Cancel(); + // only needs to be updated on the initial load, as the content won't change during pagination. + if (lastCursor == null) + SidebarMetadataUpdated?.Invoke(response.SidebarMetadata); + + // store cursor for next pagination request. lastCursor = response.Cursor; - var flow = new FillFlowContainer + LoadComponentsAsync(response.NewsPosts.Select(p => new NewsCard(p)).ToList(), loaded => { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = response.NewsPosts.Select(p => new NewsCard(p)).ToList() - }; + content.AddRange(loaded); - LoadComponentAsync(flow, loaded => - { - content.Add(loaded); showMore.IsLoading = false; - showMore.Alpha = lastCursor == null ? 0 : 1; - - if (initialLoad) - { - ResponseReceived?.Invoke(response); - initialLoad = false; - } + showMore.Alpha = response.Cursor != null ? 1 : 0; }, (cancellationToken = new CancellationTokenSource()).Token); } diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs index af3fa9c3b0..dd6de40ecb 100644 --- a/osu.Game/Overlays/NewsOverlay.cs +++ b/osu.Game/Overlays/NewsOverlay.cs @@ -133,11 +133,11 @@ namespace osu.Game.Overlays Header.SetFrontPage(); var page = new ArticleListing(year); - page.ResponseReceived += r => + page.SidebarMetadataUpdated += metadata => Schedule(() => { - sidebar.Metadata.Value = r.SidebarMetadata; + sidebar.Metadata.Value = metadata; Loading.Hide(); - }; + }); LoadDisplay(page); } From 2fdf8aa1aa49da528b1fb3d7811a957435b7be3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 15:57:31 +0900 Subject: [PATCH 24/26] Add update thread assertions --- osu.Game/Online/Spectator/SpectatorClient.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index de5e57a1d0..b90fec09d6 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -144,6 +145,8 @@ namespace osu.Game.Online.Spectator public void BeginPlaying(GameplayBeatmap beatmap, Score score) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (IsPlaying) throw new InvalidOperationException($"Cannot invoke {nameof(BeginPlaying)} when already playing"); @@ -172,6 +175,8 @@ namespace osu.Game.Online.Spectator public void WatchUser(int userId) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (watchingUsers.Contains(userId)) return; @@ -219,6 +224,8 @@ namespace osu.Game.Online.Spectator public void HandleFrame(ReplayFrame frame) { + Debug.Assert(ThreadSafety.IsUpdateThread); + if (frame is IConvertibleReplayFrame convertible) pendingFrames.Enqueue(convertible.ToLegacy(currentBeatmap)); From 7f712a4d04b64b4f9801208048f30f2a272b0a4e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 15:57:39 +0900 Subject: [PATCH 25/26] Fix EndPlaying potentially doing cross-thread mutation --- osu.Game/Online/Spectator/SpectatorClient.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b90fec09d6..48e2528528 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,10 +167,15 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - IsPlaying = false; - currentBeatmap = null; + // This method is most commonly called via Dispose(), which is asynchronous. + // Todo: This should not be a thing, but requires framework changes. + Schedule(() => + { + IsPlaying = false; + currentBeatmap = null; - EndPlayingInternal(currentState); + EndPlayingInternal(currentState); + }); } public void WatchUser(int userId) From 7c59fb37f10874db49102bf4e46e08406a385f60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 21 May 2021 16:00:58 +0900 Subject: [PATCH 26/26] Move check into callback --- osu.Game/Online/Spectator/SpectatorClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 9f270a8345..0067a55fd8 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -167,13 +167,13 @@ namespace osu.Game.Online.Spectator public void EndPlaying() { - if (!IsPlaying) - return; - // This method is most commonly called via Dispose(), which is asynchronous. // Todo: This should not be a thing, but requires framework changes. Schedule(() => { + if (!IsPlaying) + return; + IsPlaying = false; currentBeatmap = null;