From 3a6a3a067b4816b81bf1851b75640a8e73349221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 14:54:21 +0100 Subject: [PATCH 01/25] Rewrite test to cover failure case --- .../Online/TestSceneAccountCreationOverlay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index dcfe0432a8..3d65e7e4ba 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -13,13 +14,12 @@ namespace osu.Game.Tests.Visual.Online public class TestSceneAccountCreationOverlay : OsuTestScene { private readonly Container userPanelArea; + private readonly AccountCreationOverlay accountCreation; private IBindable localUser; public TestSceneAccountCreationOverlay() { - AccountCreationOverlay accountCreation; - Children = new Drawable[] { accountCreation = new AccountCreationOverlay(), @@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.Online Origin = Anchor.TopRight, }, }; - - AddStep("show", () => accountCreation.Show()); } [BackgroundDependencyLoader] @@ -42,8 +40,19 @@ namespace osu.Game.Tests.Visual.Online localUser = API.LocalUser.GetBoundCopy(); localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true); + } - AddStep("logout", API.Logout); + [Test] + public void TestOverlayVisibility() + { + AddStep("start hidden", () => accountCreation.Hide()); + AddStep("log out", API.Logout); + + AddStep("show manually", () => accountCreation.Show()); + AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible); + + AddStep("log back in", () => API.Login("dummy", "password")); + AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden); } } } From 0d8fb83d0a0a56359947ac8e52ff9e10a953db66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 15:37:14 +0100 Subject: [PATCH 02/25] Ensure account creation overlay is shown after logout Scheduling the entire API state change callback caused the scheduled hide to fire the first time the user attempted to display the account creation overlay after a logout, because the drawable wasn't present before that (so its scheduler wasn't running). It is not theoretically safe to run `Hide()` unscheduled at its present call site (as the value change callbacks are fired on the background API thread). This could also be fixed by setting `AlwaysPresent = true`, but that's a pretty ugly and unperformant change to make in general. --- osu.Game/Overlays/AccountCreationOverlay.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 58ede5502a..3084c7475a 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -93,6 +94,11 @@ namespace osu.Game.Overlays if (welcomeScreen.GetChildScreen() != null) welcomeScreen.MakeCurrent(); + + // there might be a stale scheduled hide from a previous API state change. + // cancel it here so that the overlay is not hidden again after one frame. + scheduledHide?.Cancel(); + scheduledHide = null; } protected override void PopOut() @@ -101,7 +107,9 @@ namespace osu.Game.Overlays this.FadeOut(100); } - private void apiStateChanged(ValueChangedEvent state) => Schedule(() => + private ScheduledDelegate scheduledHide; + + private void apiStateChanged(ValueChangedEvent state) { switch (state.NewValue) { @@ -113,9 +121,10 @@ namespace osu.Game.Overlays break; case APIState.Online: - Hide(); + scheduledHide?.Cancel(); + scheduledHide = Schedule(Hide); break; } - }); + } } } From 0bd9f68cbd6758faa79fbb8a3feac064edb2bdda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 18:39:55 +0100 Subject: [PATCH 03/25] Refactor update stream colour mapping code --- .../API/Requests/Responses/APIUpdateStream.cs | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs index d9e48373bb..5af7d6a01c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using Newtonsoft.Json; using osu.Framework.Graphics.Colour; using osuTK.Graphics; @@ -27,34 +28,16 @@ namespace osu.Game.Online.API.Requests.Responses public bool Equals(APIUpdateStream other) => Id == other?.Id; - public ColourInfo Colour + internal static readonly Dictionary KNOWN_STREAMS = new Dictionary { - get - { - switch (Name) - { - case "stable40": - return new Color4(102, 204, 255, 255); + ["stable40"] = new Color4(102, 204, 255, 255), + ["stable"] = new Color4(34, 153, 187, 255), + ["beta40"] = new Color4(255, 221, 85, 255), + ["cuttingedge"] = new Color4(238, 170, 0, 255), + [OsuGameBase.CLIENT_STREAM_NAME] = new Color4(237, 18, 33, 255), + ["web"] = new Color4(136, 102, 238, 255) + }; - case "stable": - return new Color4(34, 153, 187, 255); - - case "beta40": - return new Color4(255, 221, 85, 255); - - case "cuttingedge": - return new Color4(238, 170, 0, 255); - - case OsuGameBase.CLIENT_STREAM_NAME: - return new Color4(237, 18, 33, 255); - - case "web": - return new Color4(136, 102, 238, 255); - - default: - return new Color4(0, 0, 0, 255); - } - } - } + public ColourInfo Colour => KNOWN_STREAMS.TryGetValue(Name, out var colour) ? colour : new Color4(0, 0, 0, 255); } } From dacf6d5a34134b2d10070a0fc74232492e37e237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 19:28:33 +0100 Subject: [PATCH 04/25] Decouple changelog test scene from web --- .../Online/TestSceneChangelogOverlay.cs | 132 ++++++++++++------ 1 file changed, 92 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 998e42b478..9f617d49da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -1,8 +1,13 @@ // 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 Humanizer; using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Changelog; @@ -12,15 +17,63 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneChangelogOverlay : OsuTestScene { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private readonly Dictionary streams; + private readonly Dictionary builds; + + private APIChangelogBuild requestedBuild; private TestChangelogOverlay changelog; - protected override bool UseOnlineAPI => true; + public TestSceneChangelogOverlay() + { + streams = APIUpdateStream.KNOWN_STREAMS.Keys.Select((stream, id) => new APIUpdateStream + { + Id = id + 1, + Name = stream, + DisplayName = stream.Humanize(), // not quite there, but good enough. + }).ToDictionary(stream => stream.Name); + + string version = DateTimeOffset.Now.ToString("yyyy.Mdd.0"); + builds = APIUpdateStream.KNOWN_STREAMS.Keys.Select(stream => new APIChangelogBuild + { + Version = version, + DisplayVersion = version, + UpdateStream = streams[stream], + ChangelogEntries = new List() + }).ToDictionary(build => build.UpdateStream.Name); + + foreach (var stream in streams.Values) + stream.LatestBuild = builds[stream.Name]; + } [SetUp] - public void SetUp() => Schedule(() => + public void SetUp() { - Child = changelog = new TestChangelogOverlay(); - }); + requestedBuild = null; + + dummyAPI.HandleRequest = request => + { + switch (request) + { + case GetChangelogRequest changelogRequest: + var changelogResponse = new APIChangelogIndex + { + Streams = streams.Values.ToList(), + Builds = builds.Values.ToList() + }; + changelogRequest.TriggerSuccess(changelogResponse); + break; + + case GetChangelogBuildRequest buildRequest: + if (requestedBuild != null) + buildRequest.TriggerSuccess(requestedBuild); + break; + } + }; + + Schedule(() => Child = changelog = new TestChangelogOverlay()); + } [Test] public void ShowWithNoFetch() @@ -41,26 +94,22 @@ namespace osu.Game.Tests.Visual.Online } [Test] - [Ignore("needs to be updated to not be so server dependent")] public void ShowWithBuild() { - AddStep(@"Show with Lazer 2018.712.0", () => + showBuild(() => new APIChangelogBuild { - changelog.ShowBuild(new APIChangelogBuild + Version = "2018.712.0", + DisplayVersion = "2018.712.0", + UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME], + ChangelogEntries = new List { - Version = "2018.712.0", - DisplayVersion = "2018.712.0", - UpdateStream = new APIUpdateStream { Id = 5, Name = OsuGameBase.CLIENT_STREAM_NAME }, - ChangelogEntries = new List + new APIChangelogEntry { - new APIChangelogEntry - { - Category = "Test", - Title = "Title", - MessageHtml = "Message", - } + Category = "Test", + Title = "Title", + MessageHtml = "Message", } - }); + } }); AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); @@ -71,35 +120,38 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestHTMLUnescaping() { - AddStep(@"Ensure HTML string unescaping", () => + showBuild(() => new APIChangelogBuild { - changelog.ShowBuild(new APIChangelogBuild + Version = "2019.920.0", + DisplayVersion = "2019.920.0", + UpdateStream = new APIUpdateStream { - Version = "2019.920.0", - DisplayVersion = "2019.920.0", - UpdateStream = new APIUpdateStream + Name = "Test", + DisplayName = "Test" + }, + ChangelogEntries = new List + { + new APIChangelogEntry { - Name = "Test", - DisplayName = "Test" - }, - ChangelogEntries = new List - { - new APIChangelogEntry + Category = "Testing HTML strings unescaping", + Title = "Ensuring HTML strings are being unescaped", + MessageHtml = """"This text should appear triple-quoted""" >_<", + GithubUser = new APIChangelogUser { - Category = "Testing HTML strings unescaping", - Title = "Ensuring HTML strings are being unescaped", - MessageHtml = """"This text should appear triple-quoted""" >_<", - GithubUser = new APIChangelogUser - { - DisplayName = "Dummy", - OsuUsername = "Dummy", - } - }, - } - }); + DisplayName = "Dummy", + OsuUsername = "Dummy", + } + }, + } }); } + private void showBuild(Func build) + { + AddStep("set up build", () => requestedBuild = build.Invoke()); + AddStep("show build", () => changelog.ShowBuild(requestedBuild)); + } + private class TestChangelogOverlay : ChangelogOverlay { public new List Streams => base.Streams; From 5f43299d3779ffbde32d3bd2f735592f30fad820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 19:49:41 +0100 Subject: [PATCH 05/25] Fix tests failing due to base logic firing It turns out that the changelog code was semi-intentionally relying on the request to get release streams to be slow to initially show the listing of all streams. Locally suppress the base tab control logic to fix this. --- osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs index 509a6dabae..6bbff045b5 100644 --- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs +++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamControl.cs @@ -8,5 +8,11 @@ namespace osu.Game.Overlays.Changelog public class ChangelogUpdateStreamControl : OverlayStreamControl { protected override OverlayStreamItem CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value); + + protected override void LoadComplete() + { + // suppress base logic of immediately selecting first item if one exists + // (we always want to start with no stream selected). + } } } From 3ac618778f11010de62bedec719141319b37cc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:08:57 +0100 Subject: [PATCH 06/25] Handle all changelog entry types correctly --- .../Online/TestSceneChangelogOverlay.cs | 20 +++++++++++++++--- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 21 ++++++++++++++++++- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 9f617d49da..42f822664f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -105,9 +105,23 @@ namespace osu.Game.Tests.Visual.Online { new APIChangelogEntry { - Category = "Test", - Title = "Title", - MessageHtml = "Message", + Type = ChangelogEntryType.Fix, + Category = "osu!", + Title = "Fix thing", + MessageHtml = "Additional info goes here.", + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Add, + Category = "osu!", + Title = "Add thing", + Major = true + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Misc, + Category = "Code quality", + Title = "Clean up thing" } } }); diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 65ff0fef92..6ec3f08a2b 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.CentreLeft, Origin = Anchor.CentreRight, Size = new Vector2(10), - Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus, + Icon = getIconForChangelogEntry(entry.Type), Colour = entryColour.Opacity(0.5f), Margin = new MarginPadding { Right = 5 }, }, @@ -186,6 +186,25 @@ namespace osu.Game.Overlays.Changelog } } + private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) + { + // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 + switch (entryType) + { + case ChangelogEntryType.Add: + return FontAwesome.Solid.Plus; + + case ChangelogEntryType.Fix: + return FontAwesome.Solid.Check; + + case ChangelogEntryType.Misc: + return FontAwesome.Regular.Circle; + + default: + throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + } + } + protected virtual FillFlowContainer CreateHeader() => new FillFlowContainer { Anchor = Anchor.TopCentre, From 0aedc720f2c61a9f0d8d4223d3896259181ad071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:31:12 +0100 Subject: [PATCH 07/25] Extract changelog entry component --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 146 +------------ osu.Game/Overlays/Changelog/ChangelogEntry.cs | 202 ++++++++++++++++++ 2 files changed, 203 insertions(+), 145 deletions(-) create mode 100644 osu.Game/Overlays/Changelog/ChangelogEntry.cs diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index 6ec3f08a2b..2d071b7345 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -9,14 +9,8 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using System; using System.Linq; -using System.Text.RegularExpressions; using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK.Graphics; using osu.Framework.Allocation; -using System.Net; -using osuTK; -using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Overlays.Changelog { @@ -63,145 +57,7 @@ namespace osu.Game.Overlays.Changelog Margin = new MarginPadding { Top = 35, Bottom = 15 }, }); - var fontLarge = OsuFont.GetFont(size: 16); - var fontMedium = OsuFont.GetFont(size: 12); - - foreach (var entry in categoryEntries) - { - var entryColour = entry.Major ? colours.YellowLight : Color4.White; - - LinkFlowContainer title; - - var titleContainer = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Vertical = 5 }, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Size = new Vector2(10), - Icon = getIconForChangelogEntry(entry.Type), - Colour = entryColour.Opacity(0.5f), - Margin = new MarginPadding { Right = 5 }, - }, - title = new LinkFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.BottomLeft, - } - } - }; - - title.AddText(entry.Title, t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - - if (!string.IsNullOrEmpty(entry.Repository)) - { - title.AddText(" (", t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, - creationParameters: t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - title.AddText(")", t => - { - t.Font = fontLarge; - t.Colour = entryColour; - }); - } - - title.AddText("by ", t => - { - t.Font = fontMedium; - t.Colour = entryColour; - t.Padding = new MarginPadding { Left = 10 }; - }); - - if (entry.GithubUser != null) - { - if (entry.GithubUser.UserId != null) - { - title.AddUserLink(new User - { - Username = entry.GithubUser.OsuUsername, - Id = entry.GithubUser.UserId.Value - }, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - else if (entry.GithubUser.GithubUrl != null) - { - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - else - { - title.AddText(entry.GithubUser.DisplayName, t => - { - t.Font = fontMedium; - t.Colour = entryColour; - }); - } - } - - ChangelogEntries.Add(titleContainer); - - if (!string.IsNullOrEmpty(entry.MessageHtml)) - { - var message = new TextFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }; - - // todo: use markdown parsing once API returns markdown - message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => - { - t.Font = fontMedium; - t.Colour = colourProvider.Foreground1; - }); - - ChangelogEntries.Add(message); - } - } - } - } - - private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) - { - // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 - switch (entryType) - { - case ChangelogEntryType.Add: - return FontAwesome.Solid.Plus; - - case ChangelogEntryType.Fix: - return FontAwesome.Solid.Check; - - case ChangelogEntryType.Misc: - return FontAwesome.Regular.Circle; - - default: - throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + ChangelogEntries.AddRange(categoryEntries.Select(entry => new ChangelogEntry(entry))); } } diff --git a/osu.Game/Overlays/Changelog/ChangelogEntry.cs b/osu.Game/Overlays/Changelog/ChangelogEntry.cs new file mode 100644 index 0000000000..55edb40283 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogEntry.cs @@ -0,0 +1,202 @@ +// 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.Net; +using System.Text.RegularExpressions; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogEntry : FillFlowContainer + { + private readonly APIChangelogEntry entry; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private FontUsage fontLarge; + private FontUsage fontMedium; + + public ChangelogEntry(APIChangelogEntry entry) + { + this.entry = entry; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + } + + [BackgroundDependencyLoader] + private void load() + { + fontLarge = OsuFont.GetFont(size: 16); + fontMedium = OsuFont.GetFont(size: 12); + + Children = new[] + { + createTitle(), + createMessage() + }; + } + + private Drawable createTitle() + { + var entryColour = entry.Major ? colours.YellowLight : Color4.White; + + LinkFlowContainer title; + + var titleContainer = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = 5 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(10), + Icon = getIconForChangelogEntry(entry.Type), + Colour = entryColour.Opacity(0.5f), + Margin = new MarginPadding { Right = 5 }, + }, + title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.BottomLeft, + } + } + }; + + title.AddText(entry.Title, t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + + if (!string.IsNullOrEmpty(entry.Repository)) + addRepositoryReference(title, entryColour); + + if (entry.GithubUser != null) + addGithubAuthorReference(title, entryColour); + + return titleContainer; + } + + private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour) + { + title.AddText(" (", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, + t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddText(")", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + } + + private void addGithubAuthorReference(LinkFlowContainer title, Color4 entryColour) + { + title.AddText("by ", t => + { + t.Font = fontMedium; + t.Colour = entryColour; + t.Padding = new MarginPadding { Left = 10 }; + }); + + if (entry.GithubUser.UserId != null) + { + title.AddUserLink(new User + { + Username = entry.GithubUser.OsuUsername, + Id = entry.GithubUser.UserId.Value + }, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + else if (entry.GithubUser.GithubUrl != null) + { + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + else + { + title.AddText(entry.GithubUser.DisplayName, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + } + } + + private Drawable createMessage() + { + if (string.IsNullOrEmpty(entry.MessageHtml)) + return Empty(); + + var message = new TextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + // todo: use markdown parsing once API returns markdown + message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t => + { + t.Font = fontMedium; + t.Colour = colourProvider.Foreground1; + }); + + return message; + } + + private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType) + { + // compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11 + switch (entryType) + { + case ChangelogEntryType.Add: + return FontAwesome.Solid.Plus; + + case ChangelogEntryType.Fix: + return FontAwesome.Solid.Check; + + case ChangelogEntryType.Misc: + return FontAwesome.Regular.Circle; + + default: + throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}"); + } + } + } +} From c32fc05f69435a8b5adc6b7c715ce57b75cb404e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:41:35 +0100 Subject: [PATCH 08/25] Improve test scene coverage of corner cases --- .../Online/TestSceneChangelogOverlay.cs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 42f822664f..eef2892290 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -109,19 +109,43 @@ namespace osu.Game.Tests.Visual.Online Category = "osu!", Title = "Fix thing", MessageHtml = "Additional info goes here.", + Repository = "osu", + GithubPullRequestId = 11100, + GithubUser = new APIChangelogUser + { + OsuUsername = "smoogipoo", + UserId = 1040328 + } }, new APIChangelogEntry { Type = ChangelogEntryType.Add, Category = "osu!", Title = "Add thing", - Major = true + Major = true, + Repository = "ppy/osu-framework", + GithubPullRequestId = 4444, + GithubUser = new APIChangelogUser + { + DisplayName = "frenzibyte", + GithubUrl = "https://github.com/frenzibyte" + } }, new APIChangelogEntry { Type = ChangelogEntryType.Misc, Category = "Code quality", - Title = "Clean up thing" + Title = "Clean up thing", + GithubUser = new APIChangelogUser + { + DisplayName = "some dude" + } + }, + new APIChangelogEntry + { + Type = ChangelogEntryType.Misc, + Category = "Code quality", + Title = "Clean up another thing" } } }); From 09b0a57290fe224f9a45bd759ae4c85856c3f518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 25 Dec 2020 20:44:19 +0100 Subject: [PATCH 09/25] Schedule all of setup to avoid headless test fail --- osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index eef2892290..cd2c4e9346 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online } [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => { requestedBuild = null; @@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual.Online } }; - Schedule(() => Child = changelog = new TestChangelogOverlay()); - } + Child = changelog = new TestChangelogOverlay(); + }); [Test] public void ShowWithNoFetch() From ff57562956515a4895c6f50c0ec49746a2ad4963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:34:05 +0900 Subject: [PATCH 10/25] Fix multiplayer leaderboard not unsubscribing from quit users --- ...TestSceneMultiplayerGameplayLeaderboard.cs | 5 +- .../HUD/MultiplayerGameplayLeaderboard.cs | 61 ++++++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index 8078c7b994..98a3ce9b47 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -20,11 +20,12 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Online; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneMultiplayerGameplayLeaderboard : OsuTestScene + public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { [Cached(typeof(SpectatorStreamingClient))] private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16); @@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddStep("create leaderboard", () => { diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index c10ec9e004..ce6e19aea0 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; @@ -18,10 +22,21 @@ namespace osu.Game.Screens.Play.HUD { private readonly ScoreProcessor scoreProcessor; - private readonly int[] userIds; - private readonly Dictionary userScores = new Dictionary(); + [Resolved] + private SpectatorStreamingClient streamingClient { get; set; } + + [Resolved] + private StatefulMultiplayerClient multiplayerClient { get; set; } + + [Resolved] + private UserLookupCache userLookupCache { get; set; } + + private Bindable scoringMode; + + private readonly BindableList playingUsers; + /// /// Construct a new leaderboard. /// @@ -33,32 +48,24 @@ namespace osu.Game.Screens.Play.HUD this.scoreProcessor = scoreProcessor; // todo: this will likely be passed in as User instances. - this.userIds = userIds; + playingUsers = new BindableList(userIds); } - [Resolved] - private SpectatorStreamingClient streamingClient { get; set; } - - [Resolved] - private UserLookupCache userLookupCache { get; set; } - - private Bindable scoringMode; - [BackgroundDependencyLoader] private void load(OsuConfigManager config, IAPIProvider api) { streamingClient.OnNewFrames += handleIncomingFrames; - foreach (var user in userIds) + foreach (var userId in playingUsers) { - streamingClient.WatchUser(user); + streamingClient.WatchUser(userId); // probably won't be required in the final implementation. - var resolvedUser = userLookupCache.GetUserAsync(user).Result; + var resolvedUser = userLookupCache.GetUserAsync(userId).Result; var trackedUser = new TrackedUserData(); - userScores[user] = trackedUser; + userScores[userId] = trackedUser; var leaderboardScore = AddPlayer(resolvedUser, resolvedUser.Id == api.LocalUser.Value.Id); ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); @@ -70,6 +77,28 @@ namespace osu.Game.Screens.Play.HUD scoringMode.BindValueChanged(updateAllScores, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + playingUsers.BindCollectionChanged(usersChanged); + playingUsers.BindTo(multiplayerClient.PlayingUsers); + } + + private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Remove: + foreach (var userId in e.OldItems.OfType()) + { + streamingClient.StopWatchingUser(userId); + } + + break; + } + } + private void updateAllScores(ValueChangedEvent mode) { foreach (var trackedData in userScores.Values) @@ -91,7 +120,7 @@ namespace osu.Game.Screens.Play.HUD if (streamingClient != null) { - foreach (var user in userIds) + foreach (var user in playingUsers) { streamingClient.StopWatchingUser(user); } From 116acc2b5e073dc6578ad1169ce109a09bc76d24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:35:51 +0900 Subject: [PATCH 11/25] Add flow for marking user as quit for further handling --- .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index ce6e19aea0..a71c4685d9 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Play.HUD foreach (var userId in e.OldItems.OfType()) { streamingClient.StopWatchingUser(userId); + + if (userScores.TryGetValue(userId, out var trackedData)) + trackedData.MarkUserQuit(); } break; @@ -143,11 +146,19 @@ namespace osu.Game.Screens.Play.HUD private readonly BindableInt currentCombo = new BindableInt(); + public IBindable UserQuit => userQuit; + + private readonly BindableBool userQuit = new BindableBool(); + [CanBeNull] public FrameHeader LastHeader; + public void MarkUserQuit() => userQuit.Value = true; + public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { + Debug.Assert(UserQuit.Value); + if (LastHeader == null) return; From 71dcbeaf7ce57eb0aae0cd72be4f3a74a2e505bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 11:43:10 +0900 Subject: [PATCH 12/25] Mark user as quit visually on the leaderboard --- .../Screens/Play/HUD/GameplayLeaderboardScore.cs | 13 +++++++++++++ osu.Game/Screens/Play/HUD/ILeaderboardScore.cs | 2 ++ .../Play/HUD/MultiplayerGameplayLeaderboard.cs | 12 ++++++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 58281debf1..ed86f3241d 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -34,6 +34,7 @@ namespace osu.Game.Screens.Play.HUD public BindableDouble TotalScore { get; } = new BindableDouble(); public BindableDouble Accuracy { get; } = new BindableDouble(1); public BindableInt Combo { get; } = new BindableInt(); + public BindableBool HasQuit { get; } = new BindableBool(); private int? scorePosition; @@ -230,6 +231,15 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); + HasQuit.BindValueChanged(v => + { + if (v.NewValue) + { + // we will probably want to display this in a better way once we have a design. + // and also show states other than quit. + panelColour = Color4.Gray; + } + }, true); } protected override void LoadComplete() @@ -244,6 +254,9 @@ namespace osu.Game.Screens.Play.HUD private void updateColour() { + if (HasQuit.Value) + return; + if (scorePosition == 1) { mainFillContainer.ResizeWidthTo(EXTENDED_WIDTH, panel_transition_duration, Easing.OutElastic); diff --git a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs index bc1a03c5aa..83b6f6621b 100644 --- a/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/ILeaderboardScore.cs @@ -10,5 +10,7 @@ namespace osu.Game.Screens.Play.HUD BindableDouble TotalScore { get; } BindableDouble Accuracy { get; } BindableInt Combo { get; } + + BindableBool HasQuit { get; } } } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index a71c4685d9..6b0ca4d74c 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -71,6 +71,7 @@ namespace osu.Game.Screens.Play.HUD ((IBindable)leaderboardScore.Accuracy).BindTo(trackedUser.Accuracy); ((IBindable)leaderboardScore.TotalScore).BindTo(trackedUser.Score); ((IBindable)leaderboardScore.Combo).BindTo(trackedUser.CurrentCombo); + ((IBindable)leaderboardScore.HasQuit).BindTo(trackedUser.UserQuit); } scoringMode = config.GetBindable(OsuSetting.ScoreDisplayMode); @@ -81,8 +82,15 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - playingUsers.BindCollectionChanged(usersChanged); + // BindableList handles binding in a really bad way (Clear then AddRange) so we need to do this manually.. + foreach (int userId in playingUsers) + { + if (!multiplayerClient.PlayingUsers.Contains(userId)) + usersChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { userId })); + } + playingUsers.BindTo(multiplayerClient.PlayingUsers); + playingUsers.BindCollectionChanged(usersChanged); } private void usersChanged(object sender, NotifyCollectionChangedEventArgs e) @@ -157,7 +165,7 @@ namespace osu.Game.Screens.Play.HUD public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { - Debug.Assert(UserQuit.Value); + Debug.Assert(!UserQuit.Value); if (LastHeader == null) return; From 2599e95335a95ac8ae6b4a9685ac074432089a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 12:11:19 +0900 Subject: [PATCH 13/25] Add test coverage --- .../TestSceneMultiplayerGameplayLeaderboard.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index 98a3ce9b47..c214a34fe3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -27,8 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene { + private const int users = 16; + [Cached(typeof(SpectatorStreamingClient))] - private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(16); + private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming(users); [Cached(typeof(UserLookupCache))] private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache(); @@ -59,6 +61,9 @@ namespace osu.Game.Tests.Visual.Gameplay streamingClient.Start(Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + Client.PlayingUsers.Clear(); + Client.PlayingUsers.AddRange(streamingClient.PlayingUsers); + Children = new Drawable[] { scoreProcessor = new OsuScoreProcessor(), @@ -82,6 +87,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100); } + [Test] + public void TestUserQuit() + { + AddRepeatStep("mark user quit", () => Client.PlayingUsers.RemoveAt(0), users); + } + public class TestMultiplayerStreaming : SpectatorStreamingClient { public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; From 966a2151e3c2af58e9cdb506ffe8637dc5ce9fe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Dec 2020 17:55:24 +0900 Subject: [PATCH 14/25] Ensure the previous leaderboard is removed --- .../Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs index c214a34fe3..975c54c3f6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMultiplayerGameplayLeaderboard.cs @@ -54,6 +54,8 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create leaderboard", () => { + leaderboard?.Expire(); + OsuScoreProcessor scoreProcessor; Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); From 0b42b4b95598dcb067a4d06b4b734a0e468204c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Dec 2020 13:54:10 +0100 Subject: [PATCH 15/25] Rename {Drawable -> Clickable}Avatar --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 ++-- osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs | 4 ++-- .../Drawables/{DrawableAvatar.cs => ClickableAvatar.cs} | 7 ++++--- osu.Game/Users/Drawables/UpdateableAvatar.cs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) rename osu.Game/Users/Drawables/{DrawableAvatar.cs => ClickableAvatar.cs} (90%) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index d8207aa8f4..5608002513 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Leaderboards statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); - DrawableAvatar innerAvatar; + ClickableAvatar innerAvatar; Children = new Drawable[] { @@ -115,7 +115,7 @@ namespace osu.Game.Online.Leaderboards Children = new[] { avatar = new DelayedLoadWrapper( - innerAvatar = new DrawableAvatar(user) + innerAvatar = new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index 5b428a3825..00f46b0035 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Tabs if (value.Type != ChannelType.PM) throw new ArgumentException("Argument value needs to have the targettype user!"); - DrawableAvatar avatar; + ClickableAvatar avatar; AddRange(new Drawable[] { @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First()) + Child = new DelayedLoadWrapper(avatar = new ClickableAvatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, OpenOnClick = { Value = false }, diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs similarity index 90% rename from osu.Game/Users/Drawables/DrawableAvatar.cs rename to osu.Game/Users/Drawables/ClickableAvatar.cs index 42d2dbb1c6..61af2d8e27 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables { [LongRunningLoad] - public class DrawableAvatar : Container + public class ClickableAvatar : Container { /// /// Whether to open the user's profile when clicked. @@ -27,10 +27,11 @@ namespace osu.Game.Users.Drawables private OsuGame game { get; set; } /// - /// An avatar for specified user. + /// A clickable avatar for specified user, with UI sounds included. + /// If is true, clicking will open the user's profile. /// /// The user. A null value will get a placeholder avatar. - public DrawableAvatar(User user = null) + public ClickableAvatar(User user = null) { this.user = user; } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 171462f3fc..4772207edf 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -65,7 +65,7 @@ namespace osu.Game.Users.Drawables if (user == null && !ShowGuestOnNull) return null; - var avatar = new DrawableAvatar(user) + var avatar = new ClickableAvatar(user) { RelativeSizeAxes = Axes.Both, }; From e8f96b24013827b5c173159b960a0443d18ee87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Dec 2020 14:02:53 +0100 Subject: [PATCH 16/25] Bring back DrawableAvatar as a simple sprite --- osu.Game/Users/Drawables/ClickableAvatar.cs | 22 ++---------- osu.Game/Users/Drawables/DrawableAvatar.cs | 39 +++++++++++++++++++++ 2 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Users/Drawables/DrawableAvatar.cs diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 61af2d8e27..0fca9c7c9b 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -1,19 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables { - [LongRunningLoad] public class ClickableAvatar : Container { /// @@ -27,7 +24,7 @@ namespace osu.Game.Users.Drawables private OsuGame game { get; set; } /// - /// A clickable avatar for specified user, with UI sounds included. + /// A clickable avatar for the specified user, with UI sounds included. /// If is true, clicking will open the user's profile. /// /// The user. A null value will get a placeholder avatar. @@ -39,28 +36,15 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); - - Texture texture = null; - if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); - texture ??= textures.Get(@"Online/avatar-guest"); - ClickableArea clickableArea; Add(clickableArea = new ClickableArea { RelativeSizeAxes = Axes.Both, - Child = new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = texture, - FillMode = FillMode.Fit, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, Action = openProfile }); + LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add); + clickableArea.Enabled.BindTo(OpenOnClick); } diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs new file mode 100644 index 0000000000..81f6ad52d9 --- /dev/null +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -0,0 +1,39 @@ +// 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.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Users.Drawables +{ + [LongRunningLoad] + public class DrawableAvatar : Sprite + { + private readonly User user; + + /// + /// A simple, non-interactable avatar sprite for the specified user. + /// + /// The user. A null value will get a placeholder avatar. + public DrawableAvatar(User user = null) + { + this.user = user; + + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + if (user != null && user.Id > 1) + Texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); + + Texture ??= textures.Get(@"Online/avatar-guest"); + } + } +} From 8ec7970b6ab10a028d4066571255587e95baf29f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Dec 2020 14:06:09 +0100 Subject: [PATCH 17/25] Move load-complete fade specification inside --- osu.Game/Users/Drawables/DrawableAvatar.cs | 6 ++++++ osu.Game/Users/Drawables/UpdateableAvatar.cs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 81f6ad52d9..3dae3afe3f 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -35,5 +35,11 @@ namespace osu.Game.Users.Drawables Texture ??= textures.Get(@"Online/avatar-guest"); } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(300, Easing.OutQuint); + } } } diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs index 4772207edf..927e48cb56 100644 --- a/osu.Game/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -70,7 +70,6 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, }; - avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); avatar.OpenOnClick.BindTo(OpenOnClick); return avatar; From 15948de2f0cbe1c64abf0b6a4a9fba813c047a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Dec 2020 14:06:23 +0100 Subject: [PATCH 18/25] Fix gameplay leaderboard avatars being clickable --- .../Screens/Play/HUD/GameplayLeaderboardScore.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 58281debf1..51b19a8d45 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container avatarContainer; + InternalChildren = new Drawable[] { mainFillContainer = new Container @@ -152,7 +154,7 @@ namespace osu.Game.Screens.Play.HUD Spacing = new Vector2(4f, 0f), Children = new Drawable[] { - new CircularContainer + avatarContainer = new CircularContainer { Masking = true, Anchor = Anchor.CentreLeft, @@ -166,11 +168,7 @@ namespace osu.Game.Screens.Play.HUD Alpha = 0.3f, RelativeSizeAxes = Axes.Both, Colour = colours.Gray4, - }, - new UpdateableAvatar(User) - { - RelativeSizeAxes = Axes.Both, - }, + } } }, usernameText = new OsuSpriteText @@ -227,6 +225,8 @@ namespace osu.Game.Screens.Play.HUD } }; + LoadComponentAsync(new DrawableAvatar(User), avatarContainer.Add); + TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); From fa0576f47f05faa4631e7a00a5a50b4c50d38eb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 13:40:02 +0900 Subject: [PATCH 19/25] Move quit colour change implementation to updateColour for better coverage --- .../Play/HUD/GameplayLeaderboardScore.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index ed86f3241d..4aeb65bb01 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -231,15 +231,7 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => - { - if (v.NewValue) - { - // we will probably want to display this in a better way once we have a design. - // and also show states other than quit. - panelColour = Color4.Gray; - } - }, true); + HasQuit.BindValueChanged(v => updateColour()); } protected override void LoadComplete() @@ -255,7 +247,14 @@ namespace osu.Game.Screens.Play.HUD private void updateColour() { if (HasQuit.Value) + { + // we will probably want to display this in a better way once we have a design. + // and also show states other than quit. + mainFillContainer.ResizeWidthTo(regular_width, panel_transition_duration, Easing.OutElastic); + panelColour = Color4.Gray; + textColour = Color4.White; return; + } if (scorePosition == 1) { From d14a8d24b5c3cb7669a3b7537ba69820aa1e0fa4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 16:42:20 +0900 Subject: [PATCH 20/25] Remove assert for now --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 6b0ca4d74c..42df0cfe8c 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -165,8 +165,6 @@ namespace osu.Game.Screens.Play.HUD public void UpdateScore(ScoreProcessor processor, ScoringMode mode) { - Debug.Assert(!UserQuit.Value); - if (LastHeader == null) return; From 1b34f2115f6ef323aa66bf07d3e0b7ac05e76606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Dec 2020 16:57:23 +0900 Subject: [PATCH 21/25] Remove dignostics using --- osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 42df0cfe8c..00e2b8bfa7 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; From 6b6b1514e2b7b2249db579297cfe94aa44a8b52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Dec 2020 12:58:37 +0100 Subject: [PATCH 22/25] Rename method to be less misleading As it doesn't only change colour, but also width. --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 4aeb65bb01..1bd279922c 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Play.HUD positionText.Text = $"#{scorePosition.Value.FormatRank()}"; positionText.FadeTo(scorePosition.HasValue ? 1 : 0); - updateColour(); + updateState(); } } @@ -231,20 +231,20 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => updateColour()); + HasQuit.BindValueChanged(v => updateState()); } protected override void LoadComplete() { base.LoadComplete(); - updateColour(); + updateState(); FinishTransforms(true); } private const double panel_transition_duration = 500; - private void updateColour() + private void updateState() { if (HasQuit.Value) { From f75dccc9e4872477c66a484ddc13ef943511e43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Dec 2020 13:00:27 +0100 Subject: [PATCH 23/25] Explicitly use discard in value changed callback --- osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs index 1bd279922c..43e259695e 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs @@ -231,7 +231,7 @@ namespace osu.Game.Screens.Play.HUD TotalScore.BindValueChanged(v => scoreText.Text = v.NewValue.ToString("N0"), true); Accuracy.BindValueChanged(v => accuracyText.Text = v.NewValue.FormatAccuracy(), true); Combo.BindValueChanged(v => comboText.Text = $"{v.NewValue}x", true); - HasQuit.BindValueChanged(v => updateState()); + HasQuit.BindValueChanged(_ => updateState()); } protected override void LoadComplete() From bdbc210f6d6cbea01a5d8106b31c34be5c43955c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 16:51:45 +0900 Subject: [PATCH 24/25] Update fastlane and dependencies --- Gemfile.lock | 56 +++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a4b49af7e4..8ac863c9a8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,27 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.2) + CFPropertyList (3.0.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.354.0) - aws-sdk-core (3.104.3) + aws-partitions (1.413.0) + aws-sdk-core (3.110.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.36.0) - aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.78.0) - aws-sdk-core (~> 3, >= 3.104.3) + aws-sdk-s3 (1.87.0) + aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.1) + aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) - babosa (1.0.3) + babosa (1.0.4) claide (1.0.3) colored (1.2) colored2 (3.1.2) @@ -29,22 +29,23 @@ GEM highline (~> 1.7.2) declarative (0.0.20) declarative-option (0.1.0) - digest-crc (0.6.1) - rake (~> 13.0) + digest-crc (0.6.3) + rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.6) - emoji_regex (3.0.0) - excon (0.76.0) - faraday (1.0.1) + emoji_regex (3.2.1) + excon (0.78.1) + faraday (1.2.0) multipart-post (>= 1.2, < 3) - faraday-cookie_jar (0.0.6) - faraday (>= 0.7.4) + ruby2_keywords + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) http-cookie (~> 1.0.0) faraday_middleware (1.0.0) faraday (~> 1.0) - fastimage (2.2.0) - fastlane (2.156.0) + fastimage (2.2.1) + fastlane (2.170.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) @@ -96,17 +97,17 @@ GEM google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.3.3) + google-cloud-env (1.4.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.27.0) + google-cloud-storage (1.29.2) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.13.1) + googleauth (0.14.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -118,10 +119,10 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.4.0) - json (2.3.1) - jwt (2.2.1) + json (2.5.1) + jwt (2.2.2) memoist (0.16.2) - mini_magick (4.10.1) + mini_magick (4.11.0) mini_mime (1.0.2) mini_portile2 (2.4.0) multi_json (1.15.0) @@ -132,14 +133,15 @@ GEM mini_portile2 (~> 2.4.0) os (1.1.1) plist (3.5.0) - public_suffix (4.0.5) - rake (13.0.1) + public_suffix (4.0.6) + rake (13.0.3) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) + ruby2_keywords (0.0.2) rubyzip (2.3.0) security (0.1.3) signet (0.14.0) @@ -168,7 +170,7 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.18.0) + xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 2e3537e9664d24dbb6f5fec80e0c9a3e42cb1664 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Dec 2020 16:52:54 +0900 Subject: [PATCH 25/25] 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 fc01f9bf1d..cd2ce58c55 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cbf9f6f1bd..3e1b56c29c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index adbcc0ef1c..85ba0590ea 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -88,7 +88,7 @@ - +