From c124034cf3f671cc44b950851bb6a2f3900524cc Mon Sep 17 00:00:00 2001 From: dekrain Date: Wed, 16 Feb 2022 23:18:14 +0100 Subject: [PATCH 01/14] Add text displaying recent score time --- .../Online/Leaderboards/LeaderboardScore.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c2393a5de5..f77982535d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -29,6 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Leaderboards { @@ -42,6 +44,7 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; + private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -100,10 +103,18 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = date_width, + Child = new DateLabel(Score.Date), + }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, }, + Padding = new MarginPadding { Left = rank_width, Right = date_width, }, Children = new Drawable[] { new Container @@ -377,6 +388,36 @@ namespace osu.Game.Online.Leaderboards public LocalisableString TooltipText { get; } } + private class DateLabel : DrawableDate + { + public DateLabel(DateTimeOffset date) + : base(date, 20) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override string Format() + { + var now = DateTime.Now; + var difference = now - Date; + + // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference.TotalMinutes < 1) + return CommonStrings.TimeNow.ToString(); + if (difference.TotalHours < 1) + return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + if (difference.TotalDays < 1) + return $@"{Math.Ceiling(difference.TotalHours)}h"; + if (difference.TotalDays < 7) + return $@"{Math.Ceiling(difference.TotalDays)}d"; + + return string.Empty; + } + } + public class LeaderboardScoreStatistic { public IconUsage Icon; From 333a305af3dcabba5591b17140be49ba4c5c14f3 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 00:09:17 +0100 Subject: [PATCH 02/14] Use floor instead of ceiling --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f77982535d..9ac1ab8075 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -408,11 +408,11 @@ namespace osu.Game.Online.Leaderboards if (difference.TotalMinutes < 1) return CommonStrings.TimeNow.ToString(); if (difference.TotalHours < 1) - return $@"{Math.Ceiling(difference.TotalMinutes)}min"; + return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) - return $@"{Math.Ceiling(difference.TotalHours)}h"; + return $@"{Math.Floor(difference.TotalHours)}h"; if (difference.TotalDays < 7) - return $@"{Math.Ceiling(difference.TotalDays)}d"; + return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; } From 7bd731ae08210e58d2101aa3afef7807e15e4fa0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:12:35 +0100 Subject: [PATCH 03/14] Move the date next to the flag icon --- .../Online/Leaderboards/LeaderboardScore.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9ac1ab8075..a13d8eeffd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -44,7 +44,6 @@ namespace osu.Game.Online.Leaderboards private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 35; - private const float date_width = 35; protected Container RankContainer { get; private set; } @@ -59,7 +58,7 @@ namespace osu.Game.Online.Leaderboards public GlowingSpriteText ScoreText { get; private set; } - private Container flagBadgeContainer; + private FillFlowContainer flagBadgeAndDateContainer; private FillFlowContainer modsContainer; private List statisticsLabels; @@ -103,18 +102,10 @@ namespace osu.Game.Online.Leaderboards RelativeSizeAxes = Axes.Y, Width = rank_width, }, - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = date_width, - Child = new DateLabel(Score.Date), - }, content = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = rank_width, Right = date_width, }, + Padding = new MarginPadding { Left = rank_width }, Children = new Drawable[] { new Container @@ -176,10 +167,12 @@ namespace osu.Game.Online.Leaderboards Spacing = new Vector2(10f, 0f), Children = new Drawable[] { - flagBadgeContainer = new Container + flagBadgeAndDateContainer = new FillFlowContainer { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), Size = new Vector2(87f, 20f), Masking = true, Children = new Drawable[] @@ -189,6 +182,10 @@ namespace osu.Game.Online.Leaderboards Width = 30, RelativeSizeAxes = Axes.Y, }, + new DateLabel(Score.Date) + { + RelativeSizeAxes = Axes.Y, + }, }, }, new FillFlowContainer @@ -254,7 +251,7 @@ namespace osu.Game.Online.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) + foreach (var d in new[] { avatar, nameLabel, ScoreText, scoreRank, flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -281,7 +278,7 @@ namespace osu.Game.Online.Leaderboards using (BeginDelayedSequence(50)) { - var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); + var drawables = new Drawable[] { flagBadgeAndDateContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -391,10 +388,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) - : base(date, 20) + : base(date) { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } protected override string Format() @@ -411,7 +407,7 @@ namespace osu.Game.Online.Leaderboards return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 7) + if (difference.TotalDays < 3) return $@"{Math.Floor(difference.TotalDays)}d"; return string.Empty; From f4d1e6f600343fda0ca9446297e9f8d0e5073ce6 Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 10:38:29 +0100 Subject: [PATCH 04/14] Add tests for timerefs --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 8 ++++++++ osu.Game/Online/Leaderboards/LeaderboardScore.cs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 667fd08084..2f44633f4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,6 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now, Mods = new Mod[] { new OsuModHidden(), @@ -234,6 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-30), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -254,6 +256,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddSeconds(-70), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -275,6 +278,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMinutes(-40), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -296,6 +300,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 1, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -317,6 +322,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9826, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-25), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -338,6 +344,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.9654, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-50), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -359,6 +366,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddHours(-72), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a13d8eeffd..8cfba68ea6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -401,8 +401,10 @@ namespace osu.Game.Online.Leaderboards // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) // requires pluralisable string support framework-side - if (difference.TotalMinutes < 1) + if (difference.TotalSeconds < 10) return CommonStrings.TimeNow.ToString(); + if (difference.TotalMinutes < 1) + return $@"{Math.Floor(difference.TotalSeconds)}s"; if (difference.TotalHours < 1) return $@"{Math.Floor(difference.TotalMinutes)}min"; if (difference.TotalDays < 1) From 1abbb9ab39675f4caeba0b9700aa9fb4fd9ca0be Mon Sep 17 00:00:00 2001 From: dekrain Date: Thu, 17 Feb 2022 21:26:59 +0100 Subject: [PATCH 05/14] Align the bar to be on baseline of score components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Online/Leaderboards/LeaderboardScore.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 8cfba68ea6..4f7670f098 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -160,8 +160,8 @@ namespace osu.Game.Online.Leaderboards }, new FillFlowContainer { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), @@ -169,29 +169,32 @@ namespace osu.Game.Online.Leaderboards { flagBadgeAndDateContainer = new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(5f, 0f), - Size = new Vector2(87f, 20f), + Width = 87f, Masking = true, Children = new Drawable[] { new UpdateableFlag(user.Country) { - Width = 30, - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(30f, 20f), }, new DateLabel(Score.Date) { - RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, }, }, new FillFlowContainer { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Margin = new MarginPadding { Left = edge_margin }, From 15ed9ec4fa725f08c6e49ca0192542f2b7aee6ac Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:02 +0100 Subject: [PATCH 06/14] Merge scoreboard and leaderboard implementations together --- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 2 + .../Online/Leaderboards/LeaderboardScore.cs | 23 +------ .../BeatmapSet/Scores/ScoreboardTime.cs | 40 +---------- osu.Game/Utils/ScoreboardTimeUtils.cs | 66 +++++++++++++++++++ 4 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 2f44633f4d..c4178143f8 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddMonths(-3), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, @@ -409,6 +410,7 @@ namespace osu.Game.Tests.Visual.SongSelect Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, + Date = DateTime.Now.AddYears(-2), Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, BeatmapInfo = beatmapInfo, Ruleset = new OsuRuleset().RulesetInfo, diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 4f7670f098..faec0abce6 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -30,7 +30,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Game.Resources.Localisation.Web; +using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { @@ -398,24 +398,7 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { - var now = DateTime.Now; - var difference = now - Date; - - // TODO(optional): support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalSeconds < 10) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalMinutes < 1) - return $@"{Math.Floor(difference.TotalSeconds)}s"; - if (difference.TotalHours < 1) - return $@"{Math.Floor(difference.TotalMinutes)}min"; - if (difference.TotalDays < 1) - return $@"{Math.Floor(difference.TotalHours)}h"; - if (difference.TotalDays < 3) - return $@"{Math.Floor(difference.TotalDays)}d"; - - return string.Empty; + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ff1d3490b4..ceb446f310 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Game.Graphics; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -16,41 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - if (difference.TotalHours < 1) - return CommonStrings.TimeNow.ToString(); - if (difference.TotalDays < 1) - return "hr".ToQuantity((int)difference.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (Date > now.AddMonths(-1)) - return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; - - for (int months = 1; months <= 11; ++months) - { - if (Date > now.AddMonths(-(months + 1))) - return months == 1 ? "1mo" : $"{months}mos"; - } - - int years = 1; - while (Date <= now.AddYears(-(years + 1))) - years += 1; - return years == 1 ? "1yr" : $"{years}yrs"; - } + => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs new file mode 100644 index 0000000000..7e84b8f2d0 --- /dev/null +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -0,0 +1,66 @@ +// 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.Game.Resources.Localisation.Web; + +#nullable enable + +namespace osu.Game.Utils +{ + public static class ScoreboardTimeUtils + { + public static string FormatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; + } + + public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + { + // This function fails if the passed in time is something close to an epoch + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + var now = DateTime.Now; + var span = now - time; + + if (span < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (span.TotalMinutes < 1) + return FormatQuantity("sec", (int)span.TotalSeconds); + if (span.TotalHours < 1) + return FormatQuantity("min", (int)span.TotalMinutes); + if (span.TotalDays < 1) + return FormatQuantity("hr", (int)span.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return FormatQuantity("dy", (int)span.TotalDays); + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return FormatQuantity("mo", months); + } + + int years = 1; + // DateTime causes a crash here for epoch + while (time <= now.AddYears(-(years + 1))) + years += 1; + return FormatQuantity("yr", years); + } + } +} From 0d83c5a39a02581c3eb5d702c143ec55c904f461 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 20:47:30 +0100 Subject: [PATCH 07/14] Add colour highlighting recent scores --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index faec0abce6..217be5a024 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -390,6 +390,9 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { + public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; + public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; + public DateLabel(DateTimeOffset date) : base(date) { @@ -398,6 +401,15 @@ namespace osu.Game.Online.Leaderboards protected override string Format() { + var now = DateTime.Now; + var difference = now - Date; + + const double seconds_to_blank = 60*45; + const double tense_factor = 2.325; + + double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); + Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); + return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } } From 31b7ce053d24cef175f3b4b399d1a9e79b86fc32 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:18:26 +0100 Subject: [PATCH 08/14] Fix CI issues --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 217be5a024..aaf889d06c 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -404,7 +404,7 @@ namespace osu.Game.Online.Leaderboards var now = DateTime.Now; var difference = now - Date; - const double seconds_to_blank = 60*45; + const double seconds_to_blank = 60 * 45; const double tense_factor = 2.325; double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 7e84b8f2d0..86639c1034 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -14,13 +14,12 @@ namespace osu.Game.Utils { if (quantity <= 1) return $@"{quantity}{template}"; + return $@"{quantity}{template}s"; } public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) { - // This function fails if the passed in time is something close to an epoch - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. // compare: @@ -57,10 +56,13 @@ namespace osu.Game.Utils } int years = 1; - // DateTime causes a crash here for epoch - while (time <= now.AddYears(-(years + 1))) + // Add upper bound to prevent a crash + while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; - return FormatQuantity("yr", years); + if (years < 20) + return FormatQuantity("yr", years); + + return "never"; } } } From 262751a98a22d87ef5406203d5b38f94e65d30e0 Mon Sep 17 00:00:00 2001 From: dekrain Date: Sat, 19 Feb 2022 21:23:35 +0100 Subject: [PATCH 09/14] Revert highlighting recent scores --- .../Online/Leaderboards/LeaderboardScore.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index aaf889d06c..a8b508829d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -390,28 +390,13 @@ namespace osu.Game.Online.Leaderboards private class DateLabel : DrawableDate { - public static readonly Colour4 COLOUR_SATURATED = Colour4.Lime; - public static readonly Colour4 COLOUR_UNSATURATED = Colour4.White; - public DateLabel(DateTimeOffset date) : base(date) { Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() - { - var now = DateTime.Now; - var difference = now - Date; - - const double seconds_to_blank = 60 * 45; - const double tense_factor = 2.325; - - double tf = Math.Pow(difference.TotalSeconds / seconds_to_blank, tense_factor); - Colour = Interpolation.ValueAt(tf, COLOUR_SATURATED, COLOUR_UNSATURATED, 0, 1); - - return ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); - } + protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic From 2ded7d281b4b36e8d92ec12d72f1ba19ee33f935 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:17:19 +0900 Subject: [PATCH 10/14] Remove unused using statement --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a8b508829d..0e9968f27f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -30,7 +30,6 @@ using osuTK; using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Utils; -using osu.Framework.Utils; namespace osu.Game.Online.Leaderboards { From 79408f6afcfd778e84c59ae329c73faf253dfa56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:30:58 +0900 Subject: [PATCH 11/14] Add xmldoc and clean up `ScoreboardTimeUtils` extension methods a touch --- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 2 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 36 +++++++++++-------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 0e9968f27f..a9f03e5c9f 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -395,7 +395,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index ceb446f310..1927e66edb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatDate(Date, TimeSpan.FromHours(1)); + => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 86639c1034..4e079ff39f 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,15 +10,13 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - public static string FormatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - - public static string FormatDate(DateTimeOffset time, TimeSpan lowerCutoff) + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) { // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. // this is intended to be a best-effort, more legible approximation of that. @@ -36,23 +34,23 @@ namespace osu.Game.Utils return CommonStrings.TimeNow.ToString(); if (span.TotalMinutes < 1) - return FormatQuantity("sec", (int)span.TotalSeconds); + return formatQuantity("sec", (int)span.TotalSeconds); if (span.TotalHours < 1) - return FormatQuantity("min", (int)span.TotalMinutes); + return formatQuantity("min", (int)span.TotalMinutes); if (span.TotalDays < 1) - return FormatQuantity("hr", (int)span.TotalHours); + return formatQuantity("hr", (int)span.TotalHours); // this is where this gets more complicated because of how the calendar works. // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years // and test against cutoff dates to determine how many months/years to show. if (time > now.AddMonths(-1)) - return FormatQuantity("dy", (int)span.TotalDays); + return formatQuantity("dy", (int)span.TotalDays); for (int months = 1; months <= 11; ++months) { if (time > now.AddMonths(-(months + 1))) - return FormatQuantity("mo", months); + return formatQuantity("mo", months); } int years = 1; @@ -60,9 +58,17 @@ namespace osu.Game.Utils while (years < 20 && time <= now.AddYears(-(years + 1))) years += 1; if (years < 20) - return FormatQuantity("yr", years); + return formatQuantity("yr", years); return "never"; } + + private static string formatQuantity(string template, int quantity) + { + if (quantity <= 1) + return $@"{quantity}{template}"; + + return $@"{quantity}{template}s"; + } } } From fc1877b6faea3ad3bb9a476fc744d85dd6a463c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:42:26 +0900 Subject: [PATCH 12/14] Move to extension method and revert logic to match previous implementation --- osu.Game/Extensions/TimeDisplayExtensions.cs | 51 ++++++++++++++++++ .../Online/Leaderboards/LeaderboardScore.cs | 3 +- .../BeatmapSet/Scores/ScoreboardTime.cs | 4 +- osu.Game/Utils/ScoreboardTimeUtils.cs | 52 ------------------- 4 files changed, 55 insertions(+), 55 deletions(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index dc05482a05..ee6f458398 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Extensions { @@ -47,5 +49,54 @@ namespace osu.Game.Extensions return new LocalisableFormattableString(timeSpan, @"mm\:ss"); } + + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) + { + var now = DateTime.Now; + var difference = now - time; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (difference.TotalMinutes < 1) + return "sec".ToQuantity((int)difference.TotalSeconds); + if (difference.TotalHours < 1) + return "min".ToQuantity((int)difference.TotalMinutes); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (time <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a9f03e5c9f..ddd9d9a2b2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -395,7 +396,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index 1927e66edb..5018fb8c70 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); + => Date.ToShortRelativeTime(TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 4e079ff39f..ec222de018 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,58 +10,6 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - /// - /// Formats a provided date to a short relative string version for compact display. - /// - /// The time to be displayed. - /// A timespan denoting the time length beneath which "now" should be displayed. - /// A short relative string representing the input time. - public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) - { - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - var now = DateTime.Now; - var span = now - time; - - if (span < lowerCutoff) - return CommonStrings.TimeNow.ToString(); - - if (span.TotalMinutes < 1) - return formatQuantity("sec", (int)span.TotalSeconds); - if (span.TotalHours < 1) - return formatQuantity("min", (int)span.TotalMinutes); - if (span.TotalDays < 1) - return formatQuantity("hr", (int)span.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (time > now.AddMonths(-1)) - return formatQuantity("dy", (int)span.TotalDays); - - for (int months = 1; months <= 11; ++months) - { - if (time > now.AddMonths(-(months + 1))) - return formatQuantity("mo", months); - } - - int years = 1; - // Add upper bound to prevent a crash - while (years < 20 && time <= now.AddYears(-(years + 1))) - years += 1; - if (years < 20) - return formatQuantity("yr", years); - - return "never"; - } private static string formatQuantity(string template, int quantity) { From 3d0caa44c84f908a013eeac29bc126cfa20bacf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 13:43:30 +0900 Subject: [PATCH 13/14] Remove unused utils class --- osu.Game/Utils/ScoreboardTimeUtils.cs | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 osu.Game/Utils/ScoreboardTimeUtils.cs diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs deleted file mode 100644 index ec222de018..0000000000 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Game.Resources.Localisation.Web; - -#nullable enable - -namespace osu.Game.Utils -{ - public static class ScoreboardTimeUtils - { - - private static string formatQuantity(string template, int quantity) - { - if (quantity <= 1) - return $@"{quantity}{template}"; - - return $@"{quantity}{template}s"; - } - } -} From 2f6e65a9a268bf3c4b29a4005556cae95d524a31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Feb 2022 16:35:39 +0900 Subject: [PATCH 14/14] Gracefully handle undefined `DateTimeOffset` values Only seems to happen in tests, but best to safeguard against this regardless. --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index ee6f458398..54af6a5942 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -58,6 +58,9 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { + if (time == default) + return "-"; + var now = DateTime.Now; var difference = now - time;