From c342030b2c084790724021aca3b0b3557c54fb76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:10:59 +0900 Subject: [PATCH 01/78] Add specific placeholder message for custom rulesets rather than showing network error --- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 3 ++- osu.Game/Online/Leaderboards/Leaderboard.cs | 5 ++++- osu.Game/Online/Leaderboards/LeaderboardState.cs | 3 ++- .../Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 11 +++++++++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 31bd3a203c..1ed6648131 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -119,7 +119,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure)); AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter)); AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn)); - AddStep(@"Unavailable", () => leaderboard.SetErrorState(LeaderboardState.Unavailable)); + AddStep(@"Ruleset unavailable", () => leaderboard.SetErrorState(LeaderboardState.RulesetUnavailable)); + AddStep(@"Beatmap unavailable", () => leaderboard.SetErrorState(LeaderboardState.BeatmapUnavailable)); AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected)); } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 5dd3e46b4a..dde53c39e4 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -319,7 +319,10 @@ namespace osu.Game.Online.Leaderboards case LeaderboardState.NoneSelected: return new MessagePlaceholder(@"Please select a beatmap!"); - case LeaderboardState.Unavailable: + case LeaderboardState.RulesetUnavailable: + return new MessagePlaceholder(@"Leaderboards are not available for this ruleset!"); + + case LeaderboardState.BeatmapUnavailable: return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); case LeaderboardState.NoScores: diff --git a/osu.Game/Online/Leaderboards/LeaderboardState.cs b/osu.Game/Online/Leaderboards/LeaderboardState.cs index 75e2c6e6db..6b07500a98 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardState.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardState.cs @@ -8,7 +8,8 @@ namespace osu.Game.Online.Leaderboards Success, Retrieving, NetworkFailure, - Unavailable, + BeatmapUnavailable, + RulesetUnavailable, NoneSelected, NoScores, NotLoggedIn, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 907a2c9bda..6daaae9d04 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -98,6 +98,7 @@ namespace osu.Game.Screens.Select.Leaderboards protected override APIRequest FetchScores(CancellationToken cancellationToken) { var fetchBeatmapInfo = BeatmapInfo; + var fetchRuleset = ruleset.Value ?? fetchBeatmapInfo.Ruleset; if (fetchBeatmapInfo == null) { @@ -117,9 +118,15 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } + if (fetchRuleset.OnlineID <= 0 || fetchRuleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + { + SetErrorState(LeaderboardState.RulesetUnavailable); + return null; + } + if (fetchBeatmapInfo.OnlineID <= 0 || fetchBeatmapInfo.Status <= BeatmapOnlineStatus.Pending) { - SetErrorState(LeaderboardState.Unavailable); + SetErrorState(LeaderboardState.BeatmapUnavailable); return null; } @@ -137,7 +144,7 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - var req = new GetScoresRequest(fetchBeatmapInfo, ruleset.Value ?? fetchBeatmapInfo.Ruleset, Scope, requestMods); + var req = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); req.Success += r => { From d4a2645510d2a7e133d60a664d7950244e9dedaa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 14:14:44 +0900 Subject: [PATCH 02/78] Add localisation support for leaderboard error text --- osu.Game/Localisation/LeaderboardStrings.cs | 49 +++++++++++++++++++ osu.Game/Online/Leaderboards/Leaderboard.cs | 15 +++--- .../Online/Placeholders/LoginPlaceholder.cs | 3 +- 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Localisation/LeaderboardStrings.cs diff --git a/osu.Game/Localisation/LeaderboardStrings.cs b/osu.Game/Localisation/LeaderboardStrings.cs new file mode 100644 index 0000000000..8e53f8e88c --- /dev/null +++ b/osu.Game/Localisation/LeaderboardStrings.cs @@ -0,0 +1,49 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class LeaderboardStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.Leaderboard"; + + /// + /// "Couldn't fetch scores!" + /// + public static LocalisableString CouldntFetchScores => new TranslatableString(getKey(@"couldnt_fetch_scores"), @"Couldn't fetch scores!"); + + /// + /// "Please select a beatmap!" + /// + public static LocalisableString PleaseSelectABeatmap => new TranslatableString(getKey(@"please_select_a_beatmap"), @"Please select a beatmap!"); + + /// + /// "Leaderboards are not available for this ruleset!" + /// + public static LocalisableString LeaderboardsAreNotAvailableForThisRuleset => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_ruleset"), @"Leaderboards are not available for this ruleset!"); + + /// + /// "Leaderboards are not available for this beatmap!" + /// + public static LocalisableString LeaderboardsAreNotAvailableForThisBeatmap => new TranslatableString(getKey(@"leaderboards_are_not_available_for_this_beatmap"), @"Leaderboards are not available for this beatmap!"); + + /// + /// "No records yet!" + /// + public static LocalisableString NoRecordsYet => new TranslatableString(getKey(@"no_records_yet"), @"No records yet!"); + + /// + /// "Please sign in to view online leaderboards!" + /// + public static LocalisableString PleaseSignInToViewOnlineLeaderboards => new TranslatableString(getKey(@"please_sign_in_to_view_online_leaderboards"), @"Please sign in to view online leaderboards!"); + + /// + /// "Please invest in an osu!supporter tag to view this leaderboard!" + /// + public static LocalisableString PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard => new TranslatableString(getKey(@"please_invest_in_an_osu_supporter_tag_to_view_this_leaderboard"), @"Please invest in an osu!supporter tag to view this leaderboard!"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index dde53c39e4..c94a6d3361 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -22,6 +22,7 @@ using osu.Game.Online.API; using osu.Game.Online.Placeholders; using osuTK; using osuTK.Graphics; +using osu.Game.Localisation; namespace osu.Game.Online.Leaderboards { @@ -311,28 +312,28 @@ namespace osu.Game.Online.Leaderboards switch (state) { case LeaderboardState.NetworkFailure: - return new ClickablePlaceholder(@"Couldn't fetch scores!", FontAwesome.Solid.Sync) + return new ClickablePlaceholder(LeaderboardStrings.CouldntFetchScores, FontAwesome.Solid.Sync) { Action = RefetchScores }; case LeaderboardState.NoneSelected: - return new MessagePlaceholder(@"Please select a beatmap!"); + return new MessagePlaceholder(LeaderboardStrings.PleaseSelectABeatmap); case LeaderboardState.RulesetUnavailable: - return new MessagePlaceholder(@"Leaderboards are not available for this ruleset!"); + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisRuleset); case LeaderboardState.BeatmapUnavailable: - return new MessagePlaceholder(@"Leaderboards are not available for this beatmap!"); + return new MessagePlaceholder(LeaderboardStrings.LeaderboardsAreNotAvailableForThisBeatmap); case LeaderboardState.NoScores: - return new MessagePlaceholder(@"No records yet!"); + return new MessagePlaceholder(LeaderboardStrings.NoRecordsYet); case LeaderboardState.NotLoggedIn: - return new LoginPlaceholder(@"Please sign in to view online leaderboards!"); + return new LoginPlaceholder(LeaderboardStrings.PleaseSignInToViewOnlineLeaderboards); case LeaderboardState.NotSupporter: - return new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!"); + return new MessagePlaceholder(LeaderboardStrings.PleaseInvestInAnOsuSupporterTagToViewThisLeaderboard); case LeaderboardState.Retrieving: return null; diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs index f8a326a52e..d03b3d8ffc 100644 --- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs +++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Overlays; namespace osu.Game.Online.Placeholders @@ -12,7 +13,7 @@ namespace osu.Game.Online.Placeholders [Resolved(CanBeNull = true)] private LoginOverlay login { get; set; } - public LoginPlaceholder(string actionMessage) + public LoginPlaceholder(LocalisableString actionMessage) : base(actionMessage, FontAwesome.Solid.UserLock) { Action = () => login?.Show(); From c06703d662b1b887330e28bee4c439c48d913d8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 18:29:07 +0900 Subject: [PATCH 03/78] Add ability to select which display the game runs on --- .../Localisation/GraphicsSettingsStrings.cs | 5 +++ .../Sections/Graphics/LayoutSettings.cs | 33 ++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 996a1350eb..1c9aa64df5 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution"); + /// + /// "Display" + /// + public static LocalisableString Display => new TranslatableString(getKey(@"display"), @"Display"); + /// /// "UI scaling" /// diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adf1453d1a..a1688b87cc 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private FillFlowContainer> scalingSettings; - private readonly IBindable currentDisplay = new Bindable(); + private readonly Bindable currentDisplay = new Bindable(); private readonly IBindableList windowModes = new BindableList(); private Bindable scalingMode; @@ -39,6 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private OsuGameBase game { get; set; } private SettingsDropdown resolutionDropdown; + private SettingsDropdown displayDropdown; private SettingsDropdown windowModeDropdown; private Bindable scalingPositionX; @@ -72,6 +73,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics ItemSource = windowModes, Current = config.GetBindable(FrameworkSetting.WindowMode), }, + displayDropdown = new DisplaySettingsDropdown + { + LabelText = GraphicsSettingsStrings.Display, + Items = host.Window?.Displays, + Current = currentDisplay, + }, resolutionDropdown = new ResolutionSettingsDropdown { LabelText = GraphicsSettingsStrings.Resolution, @@ -142,7 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown.Current.BindValueChanged(mode => { - updateResolutionDropdown(); + updateFullscreenDropdowns(); windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default; }, true); @@ -168,7 +175,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics .Distinct()); } - updateResolutionDropdown(); + updateFullscreenDropdowns(); }), true); scalingMode.BindValueChanged(mode => @@ -183,12 +190,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics // initial update bypasses transforms updateScalingModeVisibility(); - void updateResolutionDropdown() + void updateFullscreenDropdowns() { if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen) resolutionDropdown.Show(); else resolutionDropdown.Hide(); + + if (displayDropdown.Items.Count() > 1) + displayDropdown.Show(); + else + displayDropdown.Hide(); } void updateScalingModeVisibility() @@ -243,6 +255,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics public override LocalisableString TooltipText => base.TooltipText + "x"; } + private class DisplaySettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DisplaySettingsDropdownControl(); + + private class DisplaySettingsDropdownControl : DropdownControl + { + protected override LocalisableString GenerateItemText(Display item) + { + return $"{item.Index}: {item.Name} ({item.Bounds.Width}x{item.Bounds.Height})"; + } + } + } + private class ResolutionSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl(); From 1916011ebff0064370a5589a948caa33fcafde91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 19:41:47 +0900 Subject: [PATCH 04/78] Tween corner radius when scaling container becomes non-fullscreen --- osu.Game/Graphics/Containers/ScalingContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 0d543bdbc8..781e85f82e 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -56,6 +56,8 @@ namespace osu.Game.Graphics.Containers } } + private const float corner_radius = 10; + /// /// Create a new instance. /// @@ -69,7 +71,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - CornerRadius = 10, + CornerRadius = corner_radius, Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay) }; } @@ -176,6 +178,7 @@ namespace osu.Game.Graphics.Containers sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); } private class ScalingBackgroundScreen : BackgroundScreenDefault From 29ed419d537f140943e77b23a4a965a757b7ca1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:04:53 +0900 Subject: [PATCH 05/78] Change how custom scales are applied to `ScalingContainer` to allow for better transitions --- .../Graphics/Containers/ScalingContainer.cs | 45 ++++++++++++------- .../Skinning/Editor/SkinComponentToolbox.cs | 4 +- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 21 ++------- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 781e85f82e..5888be2ae7 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Screens; @@ -38,22 +39,18 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; - private bool allowScaling = true; + private RectangleF? customScale; + private bool customScaleIsRelativePosition; /// - /// Whether user scaling preferences should be applied. Enabled by default. + /// Set a custom position and scale which overrides any user specification. /// - public bool AllowScaling + public void SetCustomScale(RectangleF? scale, bool relativePosition = false) { - get => allowScaling; - set - { - if (value == allowScaling) - return; + customScale = scale; + customScaleIsRelativePosition = relativePosition; - allowScaling = value; - if (IsLoaded) Scheduler.AddOnce(updateSize); - } + if (IsLoaded) Scheduler.AddOnce(updateSize); } private const float corner_radius = 10; @@ -164,11 +161,25 @@ namespace osu.Game.Graphics.Containers backgroundStack?.FadeOut(fade_time); } - bool scaling = AllowScaling && (targetMode == null || scalingMode.Value == targetMode); + RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); - var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; - var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; - bool requiresMasking = (scaling && targetSize != Vector2.One) + if (customScale != null) + { + sizableContainer.RelativePositionAxes = customScaleIsRelativePosition ? Axes.Both : Axes.None; + + targetSize = customScale.Value; + } + else if (targetMode == null || scalingMode.Value == targetMode) + { + sizableContainer.RelativePositionAxes = Axes.Both; + + Vector2 scale = new Vector2(sizeX.Value, sizeY.Value); + Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale); + + targetSize = new RectangleF(pos, scale); + } + + bool requiresMasking = targetSize.Size != Vector2.One // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); @@ -176,8 +187,8 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + sizableContainer.MoveTo(targetSize.Location, 500, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize.Size, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 935d2756fb..ce9afd650a 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -23,6 +23,8 @@ namespace osu.Game.Skinning.Editor { public class SkinComponentToolbox : ScrollingToolboxGroup { + public const float WIDTH = 200; + public Action RequestPlacement; private const float component_display_scale = 0.8f; @@ -41,7 +43,7 @@ namespace osu.Game.Skinning.Editor : base("Components", height) { RelativeSizeAxes = Axes.None; - Width = 200; + Width = WIDTH; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 86854ab6ff..dcfe28aaea 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -100,30 +101,14 @@ namespace osu.Game.Skinning.Editor { if (visibility.NewValue == Visibility.Visible) { - updateMasking(); - target.AllowScaling = false; - target.RelativePositionAxes = Axes.Both; - - target.ScaleTo(VISIBLE_TARGET_SCALE, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); - target.MoveToX(0.095f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.SetCustomScale(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); } else { - target.AllowScaling = true; - - target.ScaleTo(1, SkinEditor.TRANSITION_DURATION, Easing.OutQuint).OnComplete(_ => updateMasking()); - target.MoveToX(0f, SkinEditor.TRANSITION_DURATION, Easing.OutQuint); + target.SetCustomScale(null); } } - private void updateMasking() - { - if (skinEditor == null) - return; - - target.Masking = skinEditor.State.Value == Visibility.Visible; - } - public void OnReleased(KeyBindingReleaseEvent e) { } From 8d7cdbd8833ba4db7990bd5d5fcbf808d5c65d21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:25:34 +0900 Subject: [PATCH 06/78] Add note about nested masking case --- .../Graphics/Containers/ScalingContainer.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 5888be2ae7..df27c561d5 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -136,7 +136,7 @@ namespace osu.Game.Graphics.Containers private void updateSize() { - const float fade_time = 500; + const float duration = 500; if (targetMode == ScalingMode.Everything) { @@ -155,10 +155,10 @@ namespace osu.Game.Graphics.Containers backgroundStack.Push(new ScalingBackgroundScreen()); } - backgroundStack.FadeIn(fade_time); + backgroundStack.FadeIn(duration); } else - backgroundStack?.FadeOut(fade_time); + backgroundStack?.FadeOut(duration); } RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); @@ -187,9 +187,14 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetSize.Location, 500, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize.Size, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); - sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, 500, requiresMasking ? Easing.OutQuart : Easing.None); + sizableContainer.MoveTo(targetSize.Location, duration, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize.Size, duration, Easing.OutQuart); + + // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. + // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. + // An example of how this can occur is it the skin editor is visible and the game screen scaling is set to "Everything". + sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) + .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } private class ScalingBackgroundScreen : BackgroundScreenDefault From b5684aaa76d8b345397eb1fff8c60dd2c25fcd60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Mar 2022 20:33:28 +0900 Subject: [PATCH 07/78] Scale -> Rect to read better --- .../Graphics/Containers/ScalingContainer.cs | 26 +++++++++---------- osu.Game/Skinning/Editor/SkinEditorOverlay.cs | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index df27c561d5..dd611b0904 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -39,16 +39,16 @@ namespace osu.Game.Graphics.Containers private BackgroundScreenStack backgroundStack; - private RectangleF? customScale; - private bool customScaleIsRelativePosition; + private RectangleF? customRect; + private bool customRectIsRelativePosition; /// /// Set a custom position and scale which overrides any user specification. /// - public void SetCustomScale(RectangleF? scale, bool relativePosition = false) + public void SetCustomRect(RectangleF? rect, bool relativePosition = false) { - customScale = scale; - customScaleIsRelativePosition = relativePosition; + customRect = rect; + customRectIsRelativePosition = relativePosition; if (IsLoaded) Scheduler.AddOnce(updateSize); } @@ -161,13 +161,13 @@ namespace osu.Game.Graphics.Containers backgroundStack?.FadeOut(duration); } - RectangleF targetSize = new RectangleF(Vector2.Zero, Vector2.One); + RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One); - if (customScale != null) + if (customRect != null) { - sizableContainer.RelativePositionAxes = customScaleIsRelativePosition ? Axes.Both : Axes.None; + sizableContainer.RelativePositionAxes = customRectIsRelativePosition ? Axes.Both : Axes.None; - targetSize = customScale.Value; + targetRect = customRect.Value; } else if (targetMode == null || scalingMode.Value == targetMode) { @@ -176,10 +176,10 @@ namespace osu.Game.Graphics.Containers Vector2 scale = new Vector2(sizeX.Value, sizeY.Value); Vector2 pos = new Vector2(posX.Value, posY.Value) * (Vector2.One - scale); - targetSize = new RectangleF(pos, scale); + targetRect = new RectangleF(pos, scale); } - bool requiresMasking = targetSize.Size != Vector2.One + bool requiresMasking = targetRect.Size != Vector2.One // For the top level scaling container, for now we apply masking if safe areas are in use. // In the future this can likely be removed as more of the actual UI supports overflowing into the safe areas. || (targetMode == ScalingMode.Everything && safeAreaPadding.Value.Total != Vector2.Zero); @@ -187,8 +187,8 @@ namespace osu.Game.Graphics.Containers if (requiresMasking) sizableContainer.Masking = true; - sizableContainer.MoveTo(targetSize.Location, duration, Easing.OutQuart); - sizableContainer.ResizeTo(targetSize.Size, duration, Easing.OutQuart); + sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); + sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index dcfe28aaea..61c363b019 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -101,11 +101,11 @@ namespace osu.Game.Skinning.Editor { if (visibility.NewValue == Visibility.Visible) { - target.SetCustomScale(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); + target.SetCustomRect(new RectangleF(0.18f, 0.1f, VISIBLE_TARGET_SCALE, VISIBLE_TARGET_SCALE), true); } else { - target.SetCustomScale(null); + target.SetCustomRect(null); } } From e14a35b469de3841c3d573758b9a5266d99abb93 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 20:32:03 +0300 Subject: [PATCH 08/78] Add failing test case --- .../Gameplay/TestSceneStoryboardSamples.cs | 59 ------------- .../TestSceneStoryboardSamplePlayback.cs | 83 +++++++++++++++++-- osu.Game/Tests/Visual/TestPlayer.cs | 3 - 3 files changed, 78 insertions(+), 67 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 88862ea28b..6457a23a1b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -1,29 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; @@ -118,59 +112,6 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample has lifetime end", () => sample.LifetimeEnd < double.MaxValue); } - [TestCase(typeof(OsuModDoubleTime), 1.5)] - [TestCase(typeof(OsuModHalfTime), 0.75)] - [TestCase(typeof(ModWindUp), 1.5)] - [TestCase(typeof(ModWindDown), 0.75)] - [TestCase(typeof(OsuModDoubleTime), 2)] - [TestCase(typeof(OsuModHalfTime), 0.5)] - [TestCase(typeof(ModWindUp), 2)] - [TestCase(typeof(ModWindDown), 0.5)] - public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) - { - GameplayClockContainer gameplayContainer = null; - StoryboardSampleInfo sampleInfo = null; - TestDrawableStoryboardSample sample = null; - - Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; - - switch (testedMod) - { - case ModRateAdjust m: - m.SpeedChange.Value = expectedRate; - break; - - case ModTimeRamp m: - m.FinalRate.Value = m.InitialRate.Value = expectedRate; - break; - } - - AddStep("setup storyboard sample", () => - { - Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, this); - SelectedMods.Value = new[] { testedMod }; - - var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - - Add(gameplayContainer = new MasterGameplayClockContainer(Beatmap.Value, 0) - { - Child = beatmapSkinSourceContainer - }); - - beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(sampleInfo = new StoryboardSampleInfo("test-sample", 1, 1)) - { - Clock = gameplayContainer.GameplayClock - }); - }); - - AddStep("start", () => gameplayContainer.Start()); - - AddAssert("sample playback rate matches mod rates", () => - testedMod != null && Precision.AlmostEquals( - sample.ChildrenOfType().First().AggregateFrequency.Value, - ((IApplicableToRate)testedMod).ApplyToRate(sampleInfo.StartTime))); - } - [Test] public void TestSamplePlaybackWithBeatmapHitsoundsOff() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 95603b5c04..7a74a00c68 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -1,17 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.Audio; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -19,6 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay { private Storyboard storyboard; + private IReadOnlyList storyboardMods; + + protected override bool HasCustomSteps => true; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -31,10 +41,13 @@ namespace osu.Game.Tests.Visual.Gameplay backgroundLayer.Add(new StoryboardSampleInfo("Intro/welcome.mp3", time: 0, volume: 20)); } + [SetUp] + public void SetUp() => Schedule(() => storyboardMods = Array.Empty()); + [Test] public void TestStoryboardSamplesStopDuringPause() { - checkForFirstSamplePlayback(); + createPlayerTest(); AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); @@ -47,26 +60,86 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestStoryboardSamplesStopOnSkip() { - checkForFirstSamplePlayback(); + createPlayerTest(true); - AddStep("skip intro", () => InputManager.Key(osuTK.Input.Key.Space)); AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); } - private void checkForFirstSamplePlayback() + [TestCase(typeof(OsuModDoubleTime), 1.5)] + [TestCase(typeof(OsuModDoubleTime), 2)] + [TestCase(typeof(OsuModHalfTime), 0.75)] + [TestCase(typeof(OsuModHalfTime), 0.5)] + public void TestStoryboardSamplesPlaybackWithRateAdjustMods(Type expectedMod, double expectedRate) { + AddStep("setup mod", () => + { + ModRateAdjust testedMod = (ModRateAdjust)Activator.CreateInstance(expectedMod).AsNonNull(); + testedMod.SpeedChange.Value = expectedRate; + storyboardMods = new[] { testedMod }; + }); + + createPlayerTest(true); + + AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => + { + return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == expectedRate); + })); + } + + [TestCase(typeof(ModWindUp), 0.5, 2)] + [TestCase(typeof(ModWindUp), 1.51, 2)] + [TestCase(typeof(ModWindDown), 2, 0.5)] + [TestCase(typeof(ModWindDown), 0.99, 0.5)] + public void TestStoryboardSamplesPlaybackWithTimeRampMods(Type expectedMod, double initialRate, double finalRate) + { + AddStep("setup mod", () => + { + ModTimeRamp testedMod = (ModTimeRamp)Activator.CreateInstance(expectedMod).AsNonNull(); + testedMod.InitialRate.Value = initialRate; + testedMod.FinalRate.Value = finalRate; + storyboardMods = new[] { testedMod }; + }); + + createPlayerTest(true); + + ModTimeRamp gameplayMod = null; + + AddUntilStep("mod speed change updated", () => + { + gameplayMod = Player.GameplayState.Mods.OfType().Single(); + return gameplayMod.SpeedChange.Value != initialRate; + }); + + AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => + { + return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == gameplayMod.SpeedChange.Value); + })); + } + + private void createPlayerTest(bool skipIntro = false) + { + CreateTest(null); + AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + + if (skipIntro) + AddStep("skip intro", () => InputManager.Key(Key.Space)); } private IEnumerable allStoryboardSamples => Player.ChildrenOfType(); protected override bool AllowFail => false; + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = SelectedMods.Value.Concat(storyboardMods).ToArray(); + return new TestPlayer(true, false); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard ?? this.storyboard, Clock, Audio); diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 368f792e28..d463905cf4 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; - /// - /// Mods from *player* (not OsuScreen). - /// public new Bindable> Mods => base.Mods; public new HUDOverlay HUDOverlay => base.HUDOverlay; From cbb8dc28914a4c7a8cb636ea5b58534869eea934 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 20:33:46 +0300 Subject: [PATCH 09/78] Fix storyboard samples rate not adjusted from actual gameplay mods --- .../Visual/Gameplay/TestSceneStoryboard.cs | 4 ++-- .../Backgrounds/BeatmapBackgroundWithStoryboard.cs | 8 +++++++- osu.Game/Screens/Play/DimmableStoryboard.cs | 9 +++++++-- osu.Game/Screens/Play/Player.cs | 2 +- .../Storyboards/Drawables/DrawableStoryboard.cs | 9 ++++++++- .../Drawables/DrawableStoryboardSample.cs | 13 ++++++++----- osu.Game/Storyboards/Storyboard.cs | 5 +++-- 7 files changed, 36 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 3b6d02c67c..014ccb1652 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(Beatmap.Value); + storyboard = working.Storyboard.CreateDrawable(SelectedMods.Value); storyboard.Passing = false; storyboardContainer.Add(storyboard); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Gameplay sb = decoder.Decode(bfr); } - storyboard = sb.CreateDrawable(Beatmap.Value); + storyboard = sb.CreateDrawable(SelectedMods.Value); storyboardContainer.Add(storyboard); decoupledClock.ChangeSource(Beatmap.Value.Track); diff --git a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs index 56ef87c1f4..7aed442800 100644 --- a/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs +++ b/osu.Game/Graphics/Backgrounds/BeatmapBackgroundWithStoryboard.cs @@ -3,12 +3,15 @@ #nullable enable +using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Storyboards.Drawables; namespace osu.Game.Graphics.Backgrounds @@ -20,6 +23,9 @@ namespace osu.Game.Graphics.Backgrounds [Resolved(CanBeNull = true)] private MusicController? musicController { get; set; } + [Resolved] + private IBindable> mods { get; set; } = null!; + public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") : base(beatmap, fallbackTextureName) { @@ -39,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds { RelativeSizeAxes = Axes.Both, Volume = { Value = 0 }, - Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock } + Child = new DrawableStoryboard(Beatmap.Storyboard, mods.Value) { Clock = storyboardClock } }, AddInternal); } diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs index f8cedddfbe..5a3ef1e9d3 100644 --- a/osu.Game/Screens/Play/DimmableStoryboard.cs +++ b/osu.Game/Screens/Play/DimmableStoryboard.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; using osu.Game.Storyboards; using osu.Game.Storyboards.Drawables; @@ -18,6 +20,8 @@ namespace osu.Game.Screens.Play public Container OverlayLayerContainer { get; private set; } private readonly Storyboard storyboard; + private readonly IReadOnlyList mods; + private DrawableStoryboard drawableStoryboard; /// @@ -28,9 +32,10 @@ namespace osu.Game.Screens.Play /// public IBindable HasStoryboardEnded = new BindableBool(true); - public DimmableStoryboard(Storyboard storyboard) + public DimmableStoryboard(Storyboard storyboard, IReadOnlyList mods) { this.storyboard = storyboard; + this.mods = mods; } [BackgroundDependencyLoader] @@ -57,7 +62,7 @@ namespace osu.Game.Screens.Play if (!ShowStoryboard.Value && !IgnoreUserSettings.Value) return; - drawableStoryboard = storyboard.CreateDrawable(); + drawableStoryboard = storyboard.CreateDrawable(mods); HasStoryboardEnded.BindTo(drawableStoryboard.HasStoryboardEnded); if (async) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d4b02622d3..8ce3f1587d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -355,7 +355,7 @@ namespace osu.Game.Screens.Play protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart); private Drawable createUnderlayComponents() => - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; + DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard, GameplayState.Mods) { RelativeSizeAxes = Axes.Both }; private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay) { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index e6528a83bd..840500347f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using osuTK; @@ -11,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; using osu.Game.Stores; @@ -50,14 +53,18 @@ namespace osu.Game.Storyboards.Drawables private double? lastEventEndTime; + [Cached(typeof(IReadOnlyList))] + public IReadOnlyList Mods { get; } + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public DrawableStoryboard(Storyboard storyboard) + public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) { Storyboard = storyboard; + Mods = mods ?? Array.Empty(); Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 672274a2ad..4e3f72512c 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -28,17 +28,20 @@ namespace osu.Game.Storyboards.Drawables LifetimeStart = sampleInfo.StartTime; } - [Resolved] - private IBindable> mods { get; set; } + [Resolved(CanBeNull = true)] + private IReadOnlyList mods { get; set; } protected override void SkinChanged(ISkinSource skin) { base.SkinChanged(skin); - foreach (var mod in mods.Value.OfType()) + if (mods != null) { - foreach (var sample in DrawableSamples) - mod.ApplyToSample(sample); + foreach (var mod in mods.OfType()) + { + foreach (var sample in DrawableSamples) + mod.ApplyToSample(sample); + } } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index c4864c0334..2faed98ae0 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; @@ -90,8 +91,8 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IWorkingBeatmap working = null) => - new DrawableStoryboard(this); + public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => + new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) { From a812ed4462a5126ff9ba2eb2d858814e59a2ddee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:40:14 +0300 Subject: [PATCH 10/78] Ensure there is at least one sample during rate assertion --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 7a74a00c68..44529aa78c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -83,9 +83,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(true); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => - { - return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == expectedRate); - })); + sound.ChildrenOfType().First().AggregateFrequency.Value == expectedRate)); } [TestCase(typeof(ModWindUp), 0.5, 2)] @@ -113,9 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => - { - return sound.ChildrenOfType().All(s => s.AggregateFrequency.Value == gameplayMod.SpeedChange.Value); - })); + sound.ChildrenOfType().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value)); } private void createPlayerTest(bool skipIntro = false) From 82bbc32d74147ce99680e4a5c99e4ffed1bb00b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:44:58 +0300 Subject: [PATCH 11/78] Remove unnecessary `Schedule` during setup --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 44529aa78c..1d50901fec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [SetUp] - public void SetUp() => Schedule(() => storyboardMods = Array.Empty()); + public void SetUp() => storyboardMods = Array.Empty(); [Test] public void TestStoryboardSamplesStopDuringPause() From bb94d68139478a11054f76dc0258e590997e6b4c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 2 Mar 2022 23:55:42 +0300 Subject: [PATCH 12/78] Separate storyboard samples and skip intro steps to own methods --- .../TestSceneStoryboardSamplePlayback.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 1d50901fec..6b74868944 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -51,20 +51,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); - AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + allStoryobardSamplesStopped(); AddStep("player resume", () => Player.Resume()); - AddUntilStep("any storyboard samples playing after resume", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + waitUntilStoryboardSamplesPlay(); } [Test] public void TestStoryboardSamplesStopOnSkip() { - createPlayerTest(true); + createPlayerTest(); - AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + skipIntro(); + allStoryobardSamplesStopped(); - AddUntilStep("any storyboard samples playing after skip", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + waitUntilStoryboardSamplesPlay(); } [TestCase(typeof(OsuModDoubleTime), 1.5)] @@ -80,7 +81,8 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardMods = new[] { testedMod }; }); - createPlayerTest(true); + createPlayerTest(); + skipIntro(); AddAssert("sample playback rate matches mod rates", () => allStoryboardSamples.All(sound => sound.ChildrenOfType().First().AggregateFrequency.Value == expectedRate)); @@ -100,7 +102,8 @@ namespace osu.Game.Tests.Visual.Gameplay storyboardMods = new[] { testedMod }; }); - createPlayerTest(true); + createPlayerTest(); + skipIntro(); ModTimeRamp gameplayMod = null; @@ -114,17 +117,20 @@ namespace osu.Game.Tests.Visual.Gameplay sound.ChildrenOfType().First().AggregateFrequency.Value == gameplayMod.SpeedChange.Value)); } - private void createPlayerTest(bool skipIntro = false) + private void createPlayerTest() { CreateTest(null); AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null); - AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); - - if (skipIntro) - AddStep("skip intro", () => InputManager.Key(Key.Space)); + waitUntilStoryboardSamplesPlay(); } + private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); + + private void allStoryobardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + + private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space)); + private IEnumerable allStoryboardSamples => Player.ChildrenOfType(); protected override bool AllowFail => false; From 2ce4faa3564f13d334e5ed282560488257f19c22 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Mar 2022 00:02:36 +0300 Subject: [PATCH 13/78] Fix typo in method name --- .../Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs index 6b74868944..909cab5e3d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("player paused", () => Player.Pause()); AddAssert("player is currently paused", () => Player.GameplayClockContainer.IsPaused.Value); - allStoryobardSamplesStopped(); + allStoryboardSamplesStopped(); AddStep("player resume", () => Player.Resume()); waitUntilStoryboardSamplesPlay(); @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(); skipIntro(); - allStoryobardSamplesStopped(); + allStoryboardSamplesStopped(); waitUntilStoryboardSamplesPlay(); } @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void waitUntilStoryboardSamplesPlay() => AddUntilStep("any storyboard samples playing", () => allStoryboardSamples.Any(sound => sound.IsPlaying)); - private void allStoryobardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); + private void allStoryboardSamplesStopped() => AddAssert("all storyboard samples stopped immediately", () => allStoryboardSamples.All(sound => !sound.IsPlaying)); private void skipIntro() => AddStep("skip intro", () => InputManager.Key(Key.Space)); From 3630ab2db2da2ee6b0291bf0e3cedfd47a8f020f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 3 Mar 2022 00:09:12 +0300 Subject: [PATCH 14/78] Remove unnecessary nullability of storyboard mods list --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 3 +-- osu.Game/Storyboards/Storyboard.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 840500347f..01e4dfca02 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -64,7 +63,7 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) { Storyboard = storyboard; - Mods = mods ?? Array.Empty(); + Mods = mods; Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 2faed98ae0..844950336d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => + public DrawableStoryboard CreateDrawable(IReadOnlyList mods) => new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) From fab9323707655e3ce08145290bfeb84f886e0f8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:08:48 +0900 Subject: [PATCH 15/78] Replace all legacy ruleset checks with a helper property call --- osu.Desktop/DiscordRichPresence.cs | 5 +---- osu.Game/Beatmaps/DifficultyRecommender.cs | 2 +- osu.Game/Rulesets/IRulesetInfo.cs | 5 +++++ osu.Game/Rulesets/RulesetInfo.cs | 5 +++++ osu.Game/Screens/Play/SoloPlayer.cs | 3 +-- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 3642f70a56..fe687e8dab 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -108,10 +108,7 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset - int onlineID = ruleset.Value.OnlineID; - bool isLegacyRuleset = onlineID >= 0 && onlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; - - presence.Assets.SmallImageKey = isLegacyRuleset ? $"mode_{onlineID}" : "mode_custom"; + presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 3949e84f4a..8c3e832293 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -83,7 +83,7 @@ namespace osu.Game.Beatmaps requestedUserId = api.LocalUser.Value.Id; // only query API for built-in rulesets - rulesets.AvailableRulesets.Where(ruleset => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID).ForEach(rulesetInfo => + rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset).ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index 60a02212fc..a8ed1683c2 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -29,5 +29,10 @@ namespace osu.Game.Rulesets string InstantiationInfo { get; } Ruleset CreateInstance(); + + /// + /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public bool IsLegacyRuleset => OnlineID >= 0 && OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 88e3988431..cf7d84c2b4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -91,6 +91,11 @@ namespace osu.Game.Rulesets Available = Available }; + /// + /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public bool IsLegacyRuleset => ((IRulesetInfo)this).IsLegacyRuleset; + public Ruleset CreateInstance() { if (!Available) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 824c0072e3..b877ee1c11 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -7,7 +7,6 @@ using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; -using osu.Game.Rulesets; using osu.Game.Scoring; namespace osu.Game.Screens.Play @@ -32,7 +31,7 @@ namespace osu.Game.Screens.Play if (beatmapId <= 0) return null; - if (rulesetId < 0 || rulesetId > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!Ruleset.Value.IsLegacyRuleset) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 6daaae9d04..95910ed0aa 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (fetchRuleset.OnlineID <= 0 || fetchRuleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!fetchRuleset.IsLegacyRuleset) { SetErrorState(LeaderboardState.RulesetUnavailable); return null; From 42e07b7308c05bd0792ceee0ae691a54f74f8e0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:15:25 +0900 Subject: [PATCH 16/78] Convert to extension method to avoid recursive calls --- osu.Desktop/DiscordRichPresence.cs | 3 ++- osu.Game/Beatmaps/DifficultyRecommender.cs | 3 ++- osu.Game/Extensions/ModelExtensions.cs | 5 +++++ osu.Game/Rulesets/IRulesetInfo.cs | 5 ----- osu.Game/Rulesets/RulesetInfo.cs | 5 ----- osu.Game/Screens/Play/SoloPlayer.cs | 3 ++- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 3 ++- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index fe687e8dab..d87b25a4c7 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; @@ -108,7 +109,7 @@ namespace osu.Desktop presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty); // update ruleset - presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; + presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom"; presence.Assets.SmallImageText = ruleset.Value.Name; client.SetPresence(presence); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 8c3e832293..93c2fccbc7 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; @@ -83,7 +84,7 @@ namespace osu.Game.Beatmaps requestedUserId = api.LocalUser.Value.Id; // only query API for built-in rulesets - rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset).ForEach(rulesetInfo => + rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo => { var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo); diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index f178a5c97b..13c25e45c8 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -72,6 +72,11 @@ namespace osu.Game.Extensions return result; } + /// + /// Check whether this 's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). + /// + public static bool IsLegacyRuleset(this IRulesetInfo ruleset) => ruleset.OnlineID >= 0 && ruleset.OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; + /// /// Check whether the online ID of two s match. /// diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index a8ed1683c2..60a02212fc 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -29,10 +29,5 @@ namespace osu.Game.Rulesets string InstantiationInfo { get; } Ruleset CreateInstance(); - - /// - /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). - /// - public bool IsLegacyRuleset => OnlineID >= 0 && OnlineID <= ILegacyRuleset.MAX_LEGACY_RULESET_ID; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index cf7d84c2b4..88e3988431 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -91,11 +91,6 @@ namespace osu.Game.Rulesets Available = Available }; - /// - /// Whether this ruleset's online ID is within the range that defines it as a legacy ruleset (ie. either osu!, osu!taiko, osu!catch or osu!mania). - /// - public bool IsLegacyRuleset => ((IRulesetInfo)this).IsLegacyRuleset; - public Ruleset CreateInstance() { if (!Available) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index b877ee1c11..a935ce49eb 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Online.Solo; @@ -31,7 +32,7 @@ namespace osu.Game.Screens.Play if (beatmapId <= 0) return null; - if (!Ruleset.Value.IsLegacyRuleset) + if (!Ruleset.Value.IsLegacyRuleset()) return null; return new CreateSoloScoreRequest(beatmapId, rulesetId, Game.VersionHash); diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 95910ed0aa..eb0addd377 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Leaderboards; @@ -118,7 +119,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (!fetchRuleset.IsLegacyRuleset) + if (!fetchRuleset.IsLegacyRuleset()) { SetErrorState(LeaderboardState.RulesetUnavailable); return null; From 29bf7d0bde958ca2d7e30515ae4086a5bfddd2ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:35:52 +0900 Subject: [PATCH 17/78] Fix shocking grammar and typos in block comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Graphics/Containers/ScalingContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index dd611b0904..248bb8ca1f 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -190,9 +190,9 @@ namespace osu.Game.Graphics.Containers sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart); sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart); - // Of note, this will not working great in the case of nested ScalingContainers where multiple are applying corner radius. - // There should likely only be masking and corner radius applied at one point in the full game stack to fix this. - // An example of how this can occur is it the skin editor is visible and the game screen scaling is set to "Everything". + // Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius. + // Masking and corner radius should likely only be applied at one point in the full game stack to fix this. + // An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything". sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None) .OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); } From cb0d643f7047d0f594a4baf76f7751cb895166ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 14:38:20 +0900 Subject: [PATCH 18/78] Add parameter xmldoc to explain what a null rect does --- osu.Game/Graphics/Containers/ScalingContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs index 248bb8ca1f..d331b818a1 100644 --- a/osu.Game/Graphics/Containers/ScalingContainer.cs +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -45,6 +45,8 @@ namespace osu.Game.Graphics.Containers /// /// Set a custom position and scale which overrides any user specification. /// + /// A rectangle with positional and sizing information for this container to conform to. null will clear the custom rect and revert to user settings. + /// Whether the position portion of the provided rect is in relative coordinate space or not. public void SetCustomRect(RectangleF? rect, bool relativePosition = false) { customRect = rect; From ab0ee265408ad406cdbd71de743c9ab1ef9e6d99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 15:13:42 +0900 Subject: [PATCH 19/78] Remove padding from distribution graph bars to fix some bars becoming invisible at low sizes --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 93885b6e02..372c0d4849 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -160,8 +160,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both; - Padding = new MarginPadding { Horizontal = 1 }; - InternalChild = new Circle { RelativeSizeAxes = Axes.Both, From 464be6e64c52b129e36c0e412b00d5f29a4205a1 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Thu, 3 Mar 2022 14:37:39 +0800 Subject: [PATCH 20/78] Only call `IUpdatableByPlayfield.Update` if the playfield isn't nested --- osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs | 11 +++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs index 9baa252caf..7cf480a11b 100644 --- a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -5,8 +5,19 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { + /// + /// An interface for s that are updated every frame by a . + /// public interface IUpdatableByPlayfield : IApplicableMod { + /// + /// Update this . + /// + /// The main + /// + /// This method is called once per frame during gameplay by the main only. + /// To access nested s, use . + /// void Update(Playfield playfield); } } diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index d0bbf859af..30e71dde1c 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -79,6 +79,11 @@ namespace osu.Game.Rulesets.UI private readonly List nestedPlayfields = new List(); + /// + /// Whether this is nested in another . + /// + public bool IsNested { get; private set; } + /// /// Whether judgements should be displayed by this and and all nested s. /// @@ -206,6 +211,8 @@ namespace osu.Game.Rulesets.UI /// The to add. protected void AddNested(Playfield otherPlayfield) { + otherPlayfield.IsNested = true; + otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements); otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r); @@ -229,7 +236,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - if (mods != null) + if (!IsNested && mods != null) { foreach (var mod in mods) { From a06d806fb911ddb1531eda8f5415c0e9a86dc7ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 15:43:01 +0900 Subject: [PATCH 21/78] Fix hit distribution graph midpoint rounding not looking great around zero Not sure this will be accepted and it's likely only ever going to show in tests, but seems to be a better approach to midpoint rounding for this case? --- .../TestSceneHitEventTimingDistributionGraph.cs | 6 ++++++ .../HitEventTimingDistributionGraph.cs | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 221001e40b..f31aec8975 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -23,6 +23,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents()); } + [Test] + public void TestManyDistributedEventsOffset() + { + createTest(CreateDistributedHitEvents(-3.5)); + } + [Test] public void TestAroundCentre() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 93885b6e02..3a4641cba9 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -64,10 +64,22 @@ namespace osu.Game.Screens.Ranking.Statistics // Prevent div-by-0 by enforcing a minimum bin size binSize = Math.Max(1, binSize); + bool roundUp = true; + foreach (var e in hitEvents) { - int binOffset = (int)Math.Round(e.TimeOffset / binSize, MidpointRounding.AwayFromZero); - bins[timing_distribution_centre_bin_index + binOffset]++; + double binOffset = e.TimeOffset / binSize; + + // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display + // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket + // so the easiest way is to cycle between downwards and upwards rounding as we process events. + if (Math.Abs(binOffset - (int)binOffset) == 0.5) + { + binOffset += Math.Sign(binOffset) * (roundUp ? 1 : 0); + roundUp = !roundUp; + } + + bins[timing_distribution_centre_bin_index + (int)binOffset]++; } int maxCount = bins.Max(); From 9c43500ad358cc50ea399af79d47fa4f7f6ace5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:23:30 +0900 Subject: [PATCH 22/78] Add ability for player loading screen settings to scroll As we add more items here this is going to become necessary. Until the design no doubt gets changed. --- osu.Game/Overlays/SettingsToolboxGroup.cs | 7 +++--- osu.Game/Screens/Play/PlayerLoader.cs | 29 +++++++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 08321f68fe..b4178359a4 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -22,8 +22,9 @@ namespace osu.Game.Overlays { public class SettingsToolboxGroup : Container, IExpandable { + public const int CONTAINER_WIDTH = 270; + private const float transition_duration = 250; - private const int container_width = 270; private const int border_thickness = 2; private const int header_height = 30; private const int corner_radius = 5; @@ -49,7 +50,7 @@ namespace osu.Game.Overlays public SettingsToolboxGroup(string title) { AutoSizeAxes = Axes.Y; - Width = container_width; + Width = CONTAINER_WIDTH; Masking = true; CornerRadius = corner_radius; BorderColour = Color4.Black; @@ -201,7 +202,5 @@ namespace osu.Game.Overlays } protected override Container Content => content; - - protected override bool OnMouseDown(MouseDownEvent e) => true; } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index f6d63a8ec5..41eb822e39 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -143,6 +143,8 @@ namespace osu.Game.Screens.Play muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); + const float padding = 25; + InternalChildren = new Drawable[] { (content = new LogoTrackingContainer @@ -158,20 +160,27 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - PlayerSettings = new FillFlowContainer + new OsuScrollContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] + RelativeSizeAxes = Axes.Y, + Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2, + Padding = new MarginPadding { Vertical = padding }, + Masking = false, + Child = PlayerSettings = new FillFlowContainer { - VisualSettings = new VisualSettings(), - AudioSettings = new AudioSettings(), - new InputSettings() - } + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Padding = new MarginPadding { Horizontal = padding }, + Children = new PlayerSettingsGroup[] + { + VisualSettings = new VisualSettings(), + AudioSettings = new AudioSettings(), + new InputSettings() + } + }, }, idleTracker = new IdleTracker(750), }), From f09a4e9c5b0e5507d9d3a22b92682a2190030832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:28:32 +0900 Subject: [PATCH 23/78] Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index ff19dd874c..f1cb0731fe 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { + LookupKeyBindings = _ => "unknown"; + LookupSkinName = _ => "unknown"; } } } From 7ee30024e881387b8ab089d2b6153a901d7cafc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:59:10 +0900 Subject: [PATCH 24/78] Restructure `OsuSliderBar` to allow for custom tooltips --- .../Graphics/UserInterface/OsuSliderBar.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 333ae4f832..17dce10cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; protected set; } /// /// Whether to format the tooltip as a percentage or the actual value. @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - updateTooltipText(value); + TooltipText = GetTooltipText(value); } private void playSample(T value) @@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private void updateTooltipText(T value) + protected virtual LocalisableString GetTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - else - { - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - if (DisplayAsPercentage) - { - TooltipText = floatValue.ToString("0%"); - } - else - { - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); - // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + if (DisplayAsPercentage) + return floatValue.ToString("0%"); - TooltipText = floatValue.ToString($"N{significantDigits}"); - } - } + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + + return floatValue.ToString($"N{significantDigits}"); } protected override void UpdateAfterChildren() From 3848964faa4050465e8ff5db0156294e8d971336 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:07:46 +0900 Subject: [PATCH 25/78] Add tooltip text for offset adjustment slider --- .../BeatmapOffsetControlStrings.cs | 12 +++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 28 ++++++++++++++++++- .../Play/PlayerSettings/PlayerSliderBar.cs | 12 ++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 7b2a9e50b2..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -29,6 +29,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + /// + /// "(hit objects appear later)" + /// + public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)"); + + /// + /// "(hit objects appear earlier)" + /// + public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..201e431367 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,12 +3,14 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -71,7 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(10), Children = new Drawable[] { - new PlayerSliderBar + new OffsetSliderBar { KeyboardStep = 5, LabelText = BeatmapOffsetControlStrings.BeatmapOffset, @@ -88,6 +90,30 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } + public class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected class CustomSliderBar : SliderBar + { + protected override LocalisableString GetTooltipText(double value) + { + return value == 0 + ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) + : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); + } + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearLater + : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + } + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 57ffe16f76..3f1a5bc0ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new SliderBar - { - RelativeSizeAxes = Axes.X - }; + protected override Drawable CreateControl() => new SliderBar(); - private class SliderBar : OsuSliderBar + protected class SliderBar : OsuSliderBar { + public SliderBar() + { + RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 5cfa8b88211d819f70375e005d531843d1b1dac1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 21:31:56 +0900 Subject: [PATCH 26/78] Revert back to `private set` Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 17dce10cf6..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; protected set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. From 36263b4dbf09bcbcaed7559be689314d40434e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 3 Mar 2022 23:09:56 +0100 Subject: [PATCH 27/78] Replace remaining manual online ID check with extension method --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 1326395695..f0ead05280 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -7,9 +7,9 @@ using System.Linq; using System.Text; using osu.Framework.Extensions; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.IO.Legacy; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using SharpCompress.Compressors.LZMA; @@ -48,7 +48,7 @@ namespace osu.Game.Scoring.Legacy if (beatmap == null && !score.Replay.Frames.All(f => f is LegacyReplayFrame)) throw new ArgumentException(@"Beatmap must be provided if frames are not already legacy frames.", nameof(beatmap)); - if (score.ScoreInfo.Ruleset.OnlineID < 0 || score.ScoreInfo.Ruleset.OnlineID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) + if (!score.ScoreInfo.Ruleset.IsLegacyRuleset()) throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } From 17729f060582ed948ec6ba32a186549dc651f6c7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 3 Mar 2022 14:53:49 -0800 Subject: [PATCH 28/78] Reword ide section of readme to always use latest version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ace47a74f..67e28dad97 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Please make sure you have the following prerequisites: - A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. - When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). -- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. ### Downloading the source code From 53f23a429bcf56f426513a5625b98937193bd380 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 3 Mar 2022 15:01:21 -0800 Subject: [PATCH 29/78] Fix full stop being inside code backticks --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67e28dad97..f64240f67a 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ git pull Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this is provided [below](#contributing). -- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations. +- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln`. This will allow access to template run configurations. You can also build and run *osu!* from the command-line with a single command: From 18b207400d267d655621511ec89e7ef77a331c59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 11:34:25 +0900 Subject: [PATCH 30/78] 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 526ce959a6..ab1bd553a8 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7dfd099df1..a2739c527b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 80600655aa..14824a5af6 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 3a37e6e8b181389426cdcff800f35817ab842a00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:00:02 +0900 Subject: [PATCH 31/78] Fix profile badges potentially showing on incorrect profile when switching users Closes https://github.com/ppy/osu/issues/17081. --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 5f513582e5..6333802549 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -63,11 +64,15 @@ namespace osu.Game.Overlays.Profile.Header }; } + private CancellationTokenSource cancellationTokenSource; + private void updateDisplay(APIUser user) { var badges = user.Badges; badgeFlowContainer.Clear(); + cancellationTokenSource?.Cancel(); + if (badges?.Length > 0) { Show(); @@ -79,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Header { // load in stable order regardless of async load order. badgeFlowContainer.Insert(displayIndex, asyncBadge); - }); + }, (cancellationTokenSource = new CancellationTokenSource()).Token); } } else From c38126ba9d708fe8250dbb76c5a8fe91cff464d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:05:02 +0900 Subject: [PATCH 32/78] Make mods argument optional for storyboard construction --- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 5 +++-- osu.Game/Storyboards/Storyboard.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 01e4dfca02..a0fb7b0b4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.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 System.Threading; @@ -60,10 +61,10 @@ namespace osu.Game.Storyboards.Drawables protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods) + public DrawableStoryboard(Storyboard storyboard, IReadOnlyList mods = null) { Storyboard = storyboard; - Mods = mods; + Mods = mods ?? Array.Empty(); Size = new Vector2(640, 480); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 844950336d..2faed98ae0 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -91,7 +91,7 @@ namespace osu.Game.Storyboards } } - public DrawableStoryboard CreateDrawable(IReadOnlyList mods) => + public DrawableStoryboard CreateDrawable(IReadOnlyList mods = null) => new DrawableStoryboard(this, mods); public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore) From abba49fd8f8bbe15b93ace48809bc342b486488a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:16:05 +0900 Subject: [PATCH 33/78] Update all usages of `OsuSlider.TooltipText` overrides to instead implement `GetTooltipText` --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 4 +++- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 2 +- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 6 ++++-- osu.Game/Rulesets/Mods/ModMuted.cs | 2 +- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 +- 10 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 36fa336d0c..ae3c279d98 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a5bc02246d..747a56f636 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 9345d3fcc7..1aaee5b540 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); + protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adf1453d1a..b572f1c6a0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - public override LocalisableString TooltipText => base.TooltipText + "x"; + protected override LocalisableString GetTooltipText(float value) => $"{base.GetTooltipText(value)}x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..971b19ca6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -135,7 +135,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; + protected override LocalisableString GetTooltipText(double value) => Current.Disabled + ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust + : $"{base.GetTooltipText(value)}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index 8aeb440be1..c80b330db6 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); + protected override LocalisableString GetTooltipText(float value) => value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index 0afbed5df5..ca59095875 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; + protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index 6290046987..c24513e7b4 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -64,12 +64,14 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; + protected override LocalisableString GetTooltipText(double value) => Current.IsDefault + ? UserInterfaceStrings.NoLimit + : base.GetTooltipText(value); } private class StarsSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + protected override LocalisableString GetTooltipText(double value) => $"{value:0.##} stars"; } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 1d33b44812..e528d8214e 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -111,6 +111,6 @@ namespace osu.Game.Rulesets.Mods public class MuteComboSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value == 0 ? "always muted" : base.TooltipText; + protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always muted" : base.GetTooltipText(value); } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 7a935eb38f..c71239ea5a 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -57,6 +57,6 @@ namespace osu.Game.Rulesets.Mods public class HiddenComboSlider : OsuSliderBar { - public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; + protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always hidden" : base.GetTooltipText(value); } } From 7854a0a9132ddfd3f8deee5ee6c3a62dcb8000e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:21:05 +0900 Subject: [PATCH 34/78] Use `double` instead of `float` for `UIHoldActivationDelay` configuration value All times use double, so let's also use double here. --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Graphics/Containers/HoldToConfirmContainer.cs | 4 ++-- .../Settings/Sections/UserInterface/GeneralSettings.cs | 8 ++++---- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..35638a025c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -140,7 +140,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); - SetDefault(OsuSetting.UIHoldActivationDelay, 200f, 0f, 500f, 50f); + SetDefault(OsuSetting.UIHoldActivationDelay, 200.0, 0.0, 500.0, 50.0); SetDefault(OsuSetting.IntroSequence, IntroSequence.Triangles); diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index fcf445a878..999dd183aa 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -30,12 +30,12 @@ namespace osu.Game.Graphics.Containers public Bindable Progress = new BindableDouble(); - private Bindable holdActivationDelay; + private Bindable holdActivationDelay; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); } protected void BeginConfirm() diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index ca59095875..c9d3b72add 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -35,18 +35,18 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface LabelText = UserInterfaceStrings.Parallax, Current = config.GetBindable(OsuSetting.MenuParallax) }, - new SettingsSlider + new SettingsSlider { LabelText = UserInterfaceStrings.HoldToConfirmActivationTime, - Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), + Current = config.GetBindable(OsuSetting.UIHoldActivationDelay), KeyboardStep = 50 }, }; } - private class TimeSlider : OsuSliderBar + private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index b0208a0ae8..e2d79b4015 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; - private Bindable holdDelay; + private Bindable holdDelay; private Bindable loginDisplayed; private ExitConfirmOverlay exitConfirmOverlay; @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader(true)] private void load(BeatmapListingOverlay beatmapListing, SettingsOverlay settings, OsuConfigManager config, SessionStatics statics) { - holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); loginDisplayed = statics.GetBindable(Static.LoginOverlayDisplayed); if (host.CanExit) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 430f001427..4087011933 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -63,11 +63,11 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private OsuConfigManager config { get; set; } - private Bindable activationDelay; + private Bindable activationDelay; protected override void LoadComplete() { - activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + activationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); activationDelay.BindValueChanged(v => { text.Text = v.NewValue > 0 From 33862fc0dbc3bca229f2cf719cd3271cf5d131ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:25:19 +0900 Subject: [PATCH 35/78] Centralise implementation of slider bars which display millisecond time values --- .../ManiaSettingsSubsection.cs | 5 ----- osu.Game/Graphics/UserInterface/TimeSlider.cs | 15 +++++++++++++++ .../Settings/Sections/Audio/OffsetSettings.cs | 7 +------ .../Sections/UserInterface/GeneralSettings.cs | 5 ----- 4 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/TimeSlider.cs diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index ae3c279d98..bd3b8c3b10 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -45,10 +45,5 @@ namespace osu.Game.Rulesets.Mania } }; } - - private class TimeSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; - } } } diff --git a/osu.Game/Graphics/UserInterface/TimeSlider.cs b/osu.Game/Graphics/UserInterface/TimeSlider.cs new file mode 100644 index 0000000000..e99345f147 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/TimeSlider.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A slider bar which displays a millisecond time value. + /// + public class TimeSlider : OsuSliderBar + { + protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 1aaee5b540..673252a99e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Children = new Drawable[] { - new SettingsSlider + new SettingsSlider { LabelText = AudioSettingsStrings.AudioOffset, Current = config.GetBindable(OsuSetting.AudioOffset), @@ -35,10 +35,5 @@ namespace osu.Game.Overlays.Settings.Sections.Audio } }; } - - private class OffsetSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); - } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index c9d3b72add..59894cbcae 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -43,10 +43,5 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, }; } - - private class TimeSlider : OsuSliderBar - { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; - } } } From ac914878b80a5efef642d1158954258bcd4af42d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:31:57 +0900 Subject: [PATCH 36/78] Move default function specifications to `OsuConfigManager` This ensures that running tests in release configuration will not fail due to the same issue being fixed in this PR. --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 -- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index f1cb0731fe..ff19dd874c 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,8 +14,6 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { - LookupKeyBindings = _ => "unknown"; - LookupSkinName = _ => "unknown"; } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..1358b41ad2 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -240,9 +240,9 @@ namespace osu.Game.Configuration }; } - public Func LookupSkinName { private get; set; } + public Func LookupSkinName { private get; set; } = _ => @"unknown"; - public Func LookupKeyBindings { get; set; } + public Func LookupKeyBindings { get; set; } = _ => @"unknown"; } // IMPORTANT: These are used in user configuration files. From 8b504bb5ac2944efffcd3a7e0a378a26db83abda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:42:07 +0900 Subject: [PATCH 37/78] Ensure rounding is still applied in non-midpoint cases --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 3a4641cba9..235eac7f78 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -75,11 +75,11 @@ namespace osu.Game.Screens.Ranking.Statistics // so the easiest way is to cycle between downwards and upwards rounding as we process events. if (Math.Abs(binOffset - (int)binOffset) == 0.5) { - binOffset += Math.Sign(binOffset) * (roundUp ? 1 : 0); + binOffset = (int)binOffset + Math.Sign(binOffset) * (roundUp ? 1 : 0); roundUp = !roundUp; } - bins[timing_distribution_centre_bin_index + (int)binOffset]++; + bins[timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero)]++; } int maxCount = bins.Max(); From 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:28:32 +0900 Subject: [PATCH 38/78] Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index ff19dd874c..f1cb0731fe 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { + LookupKeyBindings = _ => "unknown"; + LookupSkinName = _ => "unknown"; } } } From 657f2ebb9dbd355428bb6b48845b0edd83155e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 16:59:10 +0900 Subject: [PATCH 39/78] Restructure `OsuSliderBar` to allow for custom tooltips --- .../Graphics/UserInterface/OsuSliderBar.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 333ae4f832..17dce10cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; protected set; } /// /// Whether to format the tooltip as a percentage or the actual value. @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - updateTooltipText(value); + TooltipText = GetTooltipText(value); } private void playSample(T value) @@ -203,28 +203,22 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - private void updateTooltipText(T value) + protected virtual LocalisableString GetTooltipText(T value) { if (CurrentNumber.IsInteger) - TooltipText = value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - else - { - double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); + return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); - if (DisplayAsPercentage) - { - TooltipText = floatValue.ToString("0%"); - } - else - { - decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); - // Find the number of significant digits (we could have less than 5 after normalize()) - int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + if (DisplayAsPercentage) + return floatValue.ToString("0%"); - TooltipText = floatValue.ToString($"N{significantDigits}"); - } - } + decimal decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + int significantDigits = FormatUtils.FindPrecision(decimalPrecision); + + return floatValue.ToString($"N{significantDigits}"); } protected override void UpdateAfterChildren() From cc4f89eef429160c4304841ef735a0b79d048820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 17:07:46 +0900 Subject: [PATCH 40/78] Add tooltip text for offset adjustment slider --- .../BeatmapOffsetControlStrings.cs | 12 +++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 28 ++++++++++++++++++- .../Play/PlayerSettings/PlayerSliderBar.cs | 12 ++++---- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs index 7b2a9e50b2..632a1ad0ea 100644 --- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs +++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs @@ -29,6 +29,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CalibrateUsingLastPlay => new TranslatableString(getKey(@"calibrate_using_last_play"), @"Calibrate using last play"); + /// + /// "(hit objects appear later)" + /// + public static LocalisableString HitObjectsAppearLater => new TranslatableString(getKey(@"hit_objects_appear_later"), @"(hit objects appear later)"); + + /// + /// "(hit objects appear earlier)" + /// + public static LocalisableString HitObjectsAppearEarlier => new TranslatableString(getKey(@"hit_objects_appear_earlier"), @"(hit objects appear earlier)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..201e431367 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -3,12 +3,14 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; @@ -71,7 +73,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Spacing = new Vector2(10), Children = new Drawable[] { - new PlayerSliderBar + new OffsetSliderBar { KeyboardStep = 5, LabelText = BeatmapOffsetControlStrings.BeatmapOffset, @@ -88,6 +90,30 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } + public class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected class CustomSliderBar : SliderBar + { + protected override LocalisableString GetTooltipText(double value) + { + return value == 0 + ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) + : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); + } + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearLater + : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + } + } + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index 57ffe16f76..3f1a5bc0ac 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -15,13 +15,15 @@ namespace osu.Game.Screens.Play.PlayerSettings { public OsuSliderBar Bar => (OsuSliderBar)Control; - protected override Drawable CreateControl() => new SliderBar - { - RelativeSizeAxes = Axes.X - }; + protected override Drawable CreateControl() => new SliderBar(); - private class SliderBar : OsuSliderBar + protected class SliderBar : OsuSliderBar { + public SliderBar() + { + RelativeSizeAxes = Axes.X; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { From 5dca0e3377fbb69667648709022db0440fe09394 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Mar 2022 21:31:56 +0900 Subject: [PATCH 41/78] Revert back to `private set` Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 17dce10cf6..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public virtual LocalisableString TooltipText { get; protected set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. From c1c9482077655d9315803cd5239aa66c16604928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 00:14:24 +0900 Subject: [PATCH 42/78] Add note about how global audio offset is currently applied --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 07d2026c65..5f9cd0470c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -270,7 +270,13 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, ConfineMouseMode, + + /// + /// Globally applied audio offset. + /// This is added to the audio track's current time. Higher values will cause gameplay to occur earlier, relative to the audio track. + /// AudioOffset, + VolumeInactive, MenuMusic, MenuVoice, From e09dd7d8fe79ed1019f99b76946931db152eebc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:55:35 +0900 Subject: [PATCH 43/78] Fix calibrating offset from previous non-zero offset not applying adjustment correctly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 30 ++++++++++++++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 67f5db548b..4b079cbb2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestDisplay() + public void TestCalibrationFromZero() { const double average_error = -4.5; @@ -70,5 +70,33 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + /// + /// When a beatmap offset was already set, the calibration should take it into account. + /// + [Test] + public void TestCalibrationFromNonZero() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); + + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 201e431367..1d6d0acb76 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -53,6 +53,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private OsuColour colours { get; set; } = null!; private double lastPlayAverage; + private double lastPlayBeatmapOffset; private SettingsButton? useAverageButton; @@ -156,7 +157,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { @@ -213,6 +214,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } lastPlayAverage = average; + lastPlayBeatmapOffset = Current.Value; referenceScoreContainer.AddRange(new Drawable[] { @@ -225,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = -lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, }); } From 010fa7ed0185dcce7192c50abb972fb864ac1e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:09:19 +0900 Subject: [PATCH 44/78] Allow an offset to be shown on the timing distribution graph --- .../PlayerSettings/BeatmapOffsetControl.cs | 17 ++++++-- .../HitEventTimingDistributionGraph.cs | 42 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 1d6d0acb76..4af2de6409 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -54,6 +54,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private double lastPlayAverage; private double lastPlayBeatmapOffset; + private HitEventTimingDistributionGraph? lastPlayGraph; private SettingsButton? useAverageButton; @@ -109,8 +110,8 @@ namespace osu.Game.Screens.Play.PlayerSettings Debug.Assert(value != 0); return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearLater - : BeatmapOffsetControlStrings.HitObjectsAppearEarlier; + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; } } } @@ -149,6 +150,12 @@ namespace osu.Game.Screens.Play.PlayerSettings void updateOffset() { + // the last play graph is relative to the offset at the point of the last play, so we need to factor that out. + double adjustmentSinceLastPlay = lastPlayBeatmapOffset - Current.Value; + + // Negative is applied here because the play graph is considering a hit offset, not track (as we currently use for clocks). + lastPlayGraph?.UpdateOffset(-adjustmentSinceLastPlay); + // ensure the previous write has completed. ignoring performance concerns, if we don't do this, the async writes could be out of sequence. if (realmWriteTask?.IsCompleted == false) { @@ -157,7 +164,9 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); + { + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, adjustmentSinceLastPlay, Current.Precision / 2); + } realmWriteTask = realm.WriteAsync(r => { @@ -218,7 +227,7 @@ namespace osu.Game.Screens.Play.PlayerSettings referenceScoreContainer.AddRange(new Drawable[] { - new HitEventTimingDistributionGraph(hitEvents) + lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents) { RelativeSizeAxes = Axes.X, Height = 50, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index b32b11c028..48c18deaec 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -40,6 +40,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// private const float axis_points = 5; + /// + /// The currently displayed hit events. + /// private readonly IReadOnlyList hitEvents; /// @@ -51,24 +54,43 @@ namespace osu.Game.Screens.Ranking.Statistics this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } + private int[] bins; + private double binSize; + private double hitOffset; + [BackgroundDependencyLoader] private void load() { if (hitEvents == null || hitEvents.Count == 0) return; - int[] bins = new int[total_timing_distribution_bins]; + bins = new int[total_timing_distribution_bins]; - double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); + binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); // Prevent div-by-0 by enforcing a minimum bin size binSize = Math.Max(1, binSize); + Scheduler.AddOnce(updateDisplay); + } + + public void UpdateOffset(double hitOffset) + { + this.hitOffset = hitOffset; + Scheduler.AddOnce(updateDisplay); + } + + private void updateDisplay() + { bool roundUp = true; + Array.Clear(bins, 0, bins.Length); + foreach (var e in hitEvents) { - double binOffset = e.TimeOffset / binSize; + double time = e.TimeOffset + hitOffset; + + double binOffset = time / binSize; // .NET's round midpoint handling doesn't provide a behaviour that works amazingly for display // purposes here. We want midpoint rounding to roughly distribute evenly to each adjacent bucket @@ -79,13 +101,23 @@ namespace osu.Game.Screens.Ranking.Statistics roundUp = !roundUp; } - bins[timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero)]++; + int index = timing_distribution_centre_bin_index + (int)Math.Round(binOffset, MidpointRounding.AwayFromZero); + + // may be out of range when applying an offset. for such cases we can just drop the results. + if (index >= 0 && index < bins.Length) + bins[index]++; } int maxCount = bins.Max(); var bars = new Drawable[total_timing_distribution_bins]; + for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; + { + bars[i] = new Bar + { + Height = Math.Max(0.05f, (float)bins[i] / maxCount) + }; + } Container axisFlow; From c063a73742c14494e17231b1d6d933a3cc17dbf9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 13:48:42 +0900 Subject: [PATCH 45/78] Fix autosize weirdness by specifying a constant size for the x axis --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 48c18deaec..d83422a7b6 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -121,6 +121,8 @@ namespace osu.Game.Screens.Ranking.Statistics Container axisFlow; + const float axis_font_size = 12; + InternalChild = new GridContainer { Anchor = Anchor.Centre, @@ -142,7 +144,7 @@ namespace osu.Game.Screens.Ranking.Statistics axisFlow = new Container { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + Height = axis_font_size, } }, }, @@ -162,7 +164,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "0", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); for (int i = 1; i <= axis_points; i++) @@ -179,7 +181,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = -position / 2, Alpha = alpha, Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); axisFlow.Add(new OsuSpriteText @@ -190,7 +192,7 @@ namespace osu.Game.Screens.Ranking.Statistics X = position / 2, Alpha = alpha, Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); } } From d3e04fe594fe6e3a4c827aef0e9f16efd5ad9420 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:09:27 +0900 Subject: [PATCH 46/78] Colour centre bin in distribution graph differently --- .../Statistics/HitEventTimingDistributionGraph.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d83422a7b6..72d53e5d1b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Ranking.Statistics for (int i = 0; i < bars.Length; i++) { - bars[i] = new Bar + bars[i] = new Bar(i == timing_distribution_centre_bin_index) { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; @@ -199,17 +199,22 @@ namespace osu.Game.Screens.Ranking.Statistics private class Bar : CompositeDrawable { - public Bar() + public Bar(bool isCentre) { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; RelativeSizeAxes = Axes.Both; + var colour = Color4Extensions.FromHex("#66FFCC"); + + if (isCentre) + colour = colour.Lighten(1); + InternalChild = new Circle { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#66FFCC") + Colour = colour }; } } From 540d7d0e2c1b58be41d03c6026df9077f17af440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:34:33 +0900 Subject: [PATCH 47/78] Add the ability to set and show an offset value on timing distribution graph --- ...estSceneHitEventTimingDistributionGraph.cs | 5 +- .../HitEventTimingDistributionGraph.cs | 205 +++++++++++------- 2 files changed, 126 insertions(+), 84 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index f31aec8975..48ce6145c0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -17,10 +17,13 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { + private HitEventTimingDistributionGraph graph; + [Test] public void TestManyDistributedEvents() { createTest(CreateDistributedHitEvents()); + AddStep("add adjustment", () => graph.UpdateOffset(10)); } [Test] @@ -68,7 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(events) + graph = new HitEventTimingDistributionGraph(events) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 72d53e5d1b..d510d995e2 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; +using osuTK.Graphics; namespace osu.Game.Screens.Ranking.Statistics { @@ -58,6 +59,8 @@ namespace osu.Game.Screens.Ranking.Statistics private double binSize; private double hitOffset; + private Bar[] barDrawables; + [BackgroundDependencyLoader] private void load() { @@ -108,115 +111,151 @@ namespace osu.Game.Screens.Ranking.Statistics bins[index]++; } - int maxCount = bins.Max(); - var bars = new Drawable[total_timing_distribution_bins]; - - for (int i = 0; i < bars.Length; i++) + if (barDrawables != null) { - bars[i] = new Bar(i == timing_distribution_centre_bin_index) + for (int i = 0; i < barDrawables.Length; i++) { - Height = Math.Max(0.05f, (float)bins[i] / maxCount) - }; - } - - Container axisFlow; - - const float axis_font_size = 12; - - InternalChild = new GridContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Content = new[] - { - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { bars } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - Height = axis_font_size, - } - }, - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), + barDrawables[i].UpdateOffset(bins[i]); } - }; - - // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - double maxValue = timing_distribution_bins * binSize; - double axisValueStep = maxValue / axis_points; - - axisFlow.Add(new OsuSpriteText + } + else { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "0", - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + int maxCount = bins.Max(); + barDrawables = new Bar[total_timing_distribution_bins]; - for (int i = 1; i <= axis_points; i++) - { - double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); - float alpha = 1f - position * 0.8f; + for (int i = 0; i < barDrawables.Length; i++) + barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index); + + Container axisFlow; + + const float axis_font_size = 12; + + InternalChild = new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { barDrawables } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + Height = axis_font_size, + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; + + // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + double maxValue = timing_distribution_bins * binSize; + double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), + Text = "0", Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); - axisFlow.Add(new OsuSpriteText + for (int i = 1; i <= axis_points; i++) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = position / 2, - Alpha = alpha, - Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + } } } private class Bar : CompositeDrawable { - public Bar(bool isCentre) + private readonly float value; + private readonly float maxValue; + + private readonly Circle boxOriginal; + private readonly Circle boxAdjustment; + + public Bar(float value, float maxValue, bool isCentre) { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + this.value = value; + this.maxValue = maxValue; RelativeSizeAxes = Axes.Both; + Masking = true; - var colour = Color4Extensions.FromHex("#66FFCC"); - - if (isCentre) - colour = colour.Lighten(1); - - InternalChild = new Circle + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colour + boxOriginal = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), + Height = 0, + }, + boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }, }; } + + private const double duration = 300; + + protected override void LoadComplete() + { + base.LoadComplete(); + boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } + + public void UpdateOffset(float adjustment) + { + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + } } } } From 81a49057ec7e1fa089d40b103f55fbc2ae5c7f8f Mon Sep 17 00:00:00 2001 From: Riley Quinn Date: Thu, 3 Mar 2022 22:30:08 -0600 Subject: [PATCH 48/78] Fix wiki links containing locale not loading when opened from chat. --- osu.Game/Overlays/WikiOverlay.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 44713d637d..63191900d8 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -7,6 +7,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -100,7 +101,16 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); request?.Cancel(); - request = new GetWikiRequest(e.NewValue); + string[] values = e.NewValue.Split('/', 2); + + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out _)) + { + request = new GetWikiRequest(values[1], values[0]); + } + else + { + request = new GetWikiRequest(e.NewValue); + } Loading.Show(); From 92cd8ee29faf693bc6e02c87abc16b4e14ef40ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:56:46 +0900 Subject: [PATCH 49/78] Decrease overhead of hit event distribution tests --- .../TestSceneHitEventTimingDistributionGraph.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 48ce6145c0..7471b6acf2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -19,6 +20,8 @@ namespace osu.Game.Tests.Visual.Ranking { private HitEventTimingDistributionGraph graph; + private static readonly HitObject placeholder_object = new HitCircle(); + [Test] public void TestManyDistributedEvents() { @@ -35,13 +38,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAroundCentre() { - createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] public void TestZeroTimeOffset() { - createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } [Test] @@ -56,9 +59,9 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(0, 100).Select(i => { if (i % 2 == 0) - return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null); + return new HitEvent(0, HitResult.Perfect, placeholder_object, placeholder_object, null); - return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null); + return new HitEvent(30, HitResult.Miss, placeholder_object, placeholder_object, null); }).ToList()); } @@ -86,10 +89,10 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < range * 2; i++) { - int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)); + int count = (int)(Math.Pow(range - Math.Abs(i - range), 2)) / 10; for (int j = 0; j < count; j++) - hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); + hitEvents.Add(new HitEvent(centre + i - range, HitResult.Perfect, placeholder_object, placeholder_object, null)); } return hitEvents; From 2785218b7932e93e61a654388e9dea6ccdaccac9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 14:59:53 +0900 Subject: [PATCH 50/78] Only apply animation if the bar is going to be larger than the minimum height --- .../Statistics/HitEventTimingDistributionGraph.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d510d995e2..d475556c84 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -213,6 +213,8 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Circle boxOriginal; private readonly Circle boxAdjustment; + private const float minimum_height = 0.05f; + public Bar(float value, float maxValue, bool isCentre) { this.value = value; @@ -229,7 +231,7 @@ namespace osu.Game.Screens.Ranking.Statistics Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), - Height = 0, + Height = minimum_height, }, boxAdjustment = new Circle { @@ -249,7 +251,11 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void LoadComplete() { base.LoadComplete(); - boxOriginal.ResizeHeightTo(Math.Clamp(value / maxValue, 0.05f, 1), duration, Easing.OutQuint); + + float height = Math.Clamp(value / maxValue, minimum_height, 1); + + if (height > minimum_height) + boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) From 8c7b1e0aa8dcaf5a48134d53ea4a3d23a2054111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:01:54 +0900 Subject: [PATCH 51/78] Only construct the adjustment portion of bars when required --- .../HitEventTimingDistributionGraph.cs | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d475556c84..c823ed1f4c 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly float maxValue; private readonly Circle boxOriginal; - private readonly Circle boxAdjustment; + private Circle boxAdjustment; private const float minimum_height = 0.05f; @@ -233,16 +233,6 @@ namespace osu.Game.Screens.Ranking.Statistics Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), Height = minimum_height, }, - boxAdjustment = new Circle - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = Color4.Yellow, - Blending = BlendingParameters.Additive, - Alpha = 0.6f, - Height = 0, - }, }; } @@ -260,6 +250,20 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + if (boxAdjustment == null) + { + AddInternal(boxAdjustment = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = Color4.Yellow, + Blending = BlendingParameters.Additive, + Alpha = 0.6f, + Height = 0, + }); + } + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); } } From e9e92b991e0de0921e0b040be63813c951911dae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 12:55:35 +0900 Subject: [PATCH 52/78] Fix calibrating offset from previous non-zero offset not applying adjustment correctly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 30 ++++++++++++++++++- .../PlayerSettings/BeatmapOffsetControl.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 67f5db548b..4b079cbb2c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] - public void TestDisplay() + public void TestCalibrationFromZero() { const double average_error = -4.5; @@ -70,5 +70,33 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } + + /// + /// When a beatmap offset was already set, the calibration should take it into account. + /// + [Test] + public void TestCalibrationFromNonZero() + { + const double average_error = -4.5; + const double initial_offset = -2; + + AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + AddStep("Set reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + }; + }); + + AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); + AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); + + AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index dc3e80d695..fc5c50f32f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play.PlayerSettings private OsuColour colours { get; set; } = null!; private double lastPlayAverage; + private double lastPlayBeatmapOffset; private SettingsButton? useAverageButton; @@ -130,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { @@ -187,6 +188,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } lastPlayAverage = average; + lastPlayBeatmapOffset = Current.Value; referenceScoreContainer.AddRange(new Drawable[] { @@ -199,7 +201,7 @@ namespace osu.Game.Screens.Play.PlayerSettings useAverageButton = new SettingsButton { Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, - Action = () => Current.Value = -lastPlayAverage + Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, }); } From 5a164e4520aef238009bc95d398bb04c3ccaf8b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 15:19:55 +0900 Subject: [PATCH 53/78] Hide adjustment when no adjustment is applied --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index c823ed1f4c..bb42dda597 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,8 +250,13 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { + bool hasAdjustment = adjustment != value; + if (boxAdjustment == null) { + if (!hasAdjustment) + return; + AddInternal(boxAdjustment = new Circle { RelativeSizeAxes = Axes.Both, @@ -265,6 +270,7 @@ namespace osu.Game.Screens.Ranking.Statistics } boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } } From 8491bab77c47391f0f7ee386787befcb4d5dafbf Mon Sep 17 00:00:00 2001 From: Riley Quinn Date: Fri, 4 Mar 2022 00:57:13 -0600 Subject: [PATCH 54/78] Replace string locale with Language --- osu.Game/Online/API/Requests/GetWikiRequest.cs | 10 ++++++---- osu.Game/Overlays/WikiOverlay.cs | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetWikiRequest.cs b/osu.Game/Online/API/Requests/GetWikiRequest.cs index 248fcc03e3..09571ab0a8 100644 --- a/osu.Game/Online/API/Requests/GetWikiRequest.cs +++ b/osu.Game/Online/API/Requests/GetWikiRequest.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Extensions; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -8,14 +10,14 @@ namespace osu.Game.Online.API.Requests public class GetWikiRequest : APIRequest { private readonly string path; - private readonly string locale; + private readonly Language language; - public GetWikiRequest(string path, string locale = "en") + public GetWikiRequest(string path, Language language = Language.en) { this.path = path; - this.locale = locale; + this.language = language; } - protected override string Target => $"wiki/{locale}/{path}"; + protected override string Target => $"wiki/{language.ToCultureCode()}/{path}"; } } diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 63191900d8..d6a379dc56 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -103,9 +103,9 @@ namespace osu.Game.Overlays string[] values = e.NewValue.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out _)) + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) { - request = new GetWikiRequest(values[1], values[0]); + request = new GetWikiRequest(values[1], language); } else { From 76c293b9e982650e6b9b6edb576b37e427b95089 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:00:56 +0300 Subject: [PATCH 55/78] Fix cancellation token source recreated on every medal --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 6333802549..9341ad63fc 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -68,10 +68,12 @@ namespace osu.Game.Overlays.Profile.Header private void updateDisplay(APIUser user) { - var badges = user.Badges; + cancellationTokenSource?.Cancel(); + cancellationTokenSource = new CancellationTokenSource(); + badgeFlowContainer.Clear(); - cancellationTokenSource?.Cancel(); + var badges = user.Badges; if (badges?.Length > 0) { @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Header { // load in stable order regardless of async load order. badgeFlowContainer.Insert(displayIndex, asyncBadge); - }, (cancellationTokenSource = new CancellationTokenSource()).Token); + }, cancellationTokenSource.Token); } } else From 129c290ca07d3d23608615d84b0e80f3337ca96e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:01:07 +0300 Subject: [PATCH 56/78] Dispose cancellation token source on disposal --- osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index 9341ad63fc..922f3832e4 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -94,5 +94,11 @@ namespace osu.Game.Overlays.Profile.Header Hide(); } } + + protected override void Dispose(bool isDisposing) + { + cancellationTokenSource?.Cancel(); + base.Dispose(isDisposing); + } } } From 3fdc7ed9d2b80533cda71a9e4829706eab1ded51 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 10:14:19 +0300 Subject: [PATCH 57/78] Remove brackets surrounding one-line statements --- osu.Game/Overlays/WikiOverlay.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index d6a379dc56..4015d8e196 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -104,13 +104,9 @@ namespace osu.Game.Overlays string[] values = e.NewValue.Split('/', 2); if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) - { request = new GetWikiRequest(values[1], language); - } else - { request = new GetWikiRequest(e.NewValue); - } Loading.Show(); From 1c40fcb79e65b31c46dbd7fb49cbde6ce2e389b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 17:54:04 +0900 Subject: [PATCH 58/78] Reorder math to be easier to pass Co-authored-by: Salman Ahmed --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index fc5c50f32f..8253c2e38e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Play.PlayerSettings } if (useAverageButton != null) - useAverageButton.Enabled.Value = !Precision.AlmostEquals(lastPlayAverage, -Current.Value + lastPlayBeatmapOffset, Current.Precision / 2); + useAverageButton.Enabled.Value = !Precision.AlmostEquals(Current.Value, lastPlayBeatmapOffset - lastPlayAverage, Current.Precision / 2); realmWriteTask = realm.WriteAsync(r => { From 1e246bf5608676b0ca7f2c95eae5881421bc81f6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 4 Mar 2022 20:14:14 +0900 Subject: [PATCH 59/78] Reduce 'cursor-tap' sample playback volume on MouseUp --- osu.Game/Graphics/Cursor/MenuCursor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 0cc751ea21..03fad00e41 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -140,6 +140,7 @@ namespace osu.Game.Graphics.Cursor // Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird) channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75; channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range); + channel.Volume.Value = baseFrequency; channel.Play(); } From c132fc19e75752191dda416d38474f2bac0a94dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 4 Mar 2022 22:59:33 +0900 Subject: [PATCH 60/78] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 526ce959a6..d418dcaccf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7dfd099df1..64785ab566 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 80600655aa..a7bffd28b5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From 524b8e02ef5a60ac17a030983095c46259e1d6b2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Mar 2022 22:37:36 +0900 Subject: [PATCH 61/78] Revert "Update all usages of `OsuSlider.TooltipText` overrides to instead implement `GetTooltipText`" This reverts commit abba49fd8f8bbe15b93ace48809bc342b486488a. --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 2 +- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 2 +- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 4 +--- osu.Game/Overlays/Settings/Sections/SizeSlider.cs | 2 +- .../Settings/Sections/UserInterface/GeneralSettings.cs | 2 +- .../Settings/Sections/UserInterface/SongSelectSettings.cs | 6 ++---- osu.Game/Rulesets/Mods/ModMuted.cs | 2 +- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 +- 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index ae3c279d98..36fa336d0c 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => $"{value:N0} ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index 747a56f636..a5bc02246d 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightBox; private readonly Container nubContainer; - public LocalisableString TooltipText { get; private set; } + public virtual LocalisableString TooltipText { get; private set; } /// /// Whether to format the tooltip as a percentage or the actual value. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 1aaee5b540..9345d3fcc7 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class OffsetSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => value.ToString(@"0ms"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0ms"); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index b572f1c6a0..adf1453d1a 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -240,7 +240,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private class UIScaleSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{base.GetTooltipText(value)}x"; + public override LocalisableString TooltipText => base.TooltipText + "x"; } private class ResolutionSettingsDropdown : SettingsDropdown diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 971b19ca6c..4235dc0a05 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -135,9 +135,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => Current.Disabled - ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust - : $"{base.GetTooltipText(value)}x"; + public override LocalisableString TooltipText => Current.Disabled ? MouseSettingsStrings.EnableHighPrecisionForSensitivityAdjust : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs index c80b330db6..8aeb440be1 100644 --- a/osu.Game/Overlays/Settings/Sections/SizeSlider.cs +++ b/osu.Game/Overlays/Settings/Sections/SizeSlider.cs @@ -11,6 +11,6 @@ namespace osu.Game.Overlays.Settings.Sections /// internal class SizeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => value.ToString(@"0.##x"); + public override LocalisableString TooltipText => Current.Value.ToString(@"0.##x"); } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs index ca59095875..0afbed5df5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class TimeSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(float value) => $"{value:N0} ms"; + public override LocalisableString TooltipText => Current.Value.ToString(@"N0") + "ms"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs index c24513e7b4..6290046987 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs @@ -64,14 +64,12 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface private class MaximumStarsSlider : StarsSlider { - protected override LocalisableString GetTooltipText(double value) => Current.IsDefault - ? UserInterfaceStrings.NoLimit - : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText; } private class StarsSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(double value) => $"{value:0.##} stars"; + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index e528d8214e..1d33b44812 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -111,6 +111,6 @@ namespace osu.Game.Rulesets.Mods public class MuteComboSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always muted" : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.Value == 0 ? "always muted" : base.TooltipText; } } diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index c71239ea5a..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -57,6 +57,6 @@ namespace osu.Game.Rulesets.Mods public class HiddenComboSlider : OsuSliderBar { - protected override LocalisableString GetTooltipText(int value) => value == 0 ? "always hidden" : base.GetTooltipText(value); + public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText; } } From 1e34aca984061824993b490cd2b4b70e4242af5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Mar 2022 14:38:15 +0100 Subject: [PATCH 62/78] Rename method to better fit purpose --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a1688b87cc..602ace6dea 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown.Current.BindValueChanged(mode => { - updateFullscreenDropdowns(); + updateDisplayModeDropdowns(); windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default; }, true); @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics .Distinct()); } - updateFullscreenDropdowns(); + updateDisplayModeDropdowns(); }), true); scalingMode.BindValueChanged(mode => @@ -190,7 +190,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics // initial update bypasses transforms updateScalingModeVisibility(); - void updateFullscreenDropdowns() + void updateDisplayModeDropdowns() { if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen) resolutionDropdown.Show(); From ce51ce49cf1cf4cf109ec92a180314af93d41d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 5 Mar 2022 22:46:13 +0900 Subject: [PATCH 63/78] Revert changes to `GetTooltipText` and use `TooltipText` override directly --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 6 +++--- .../Play/PlayerSettings/BeatmapOffsetControl.cs | 10 ++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a5bc02246d..21c8dfcfa4 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -148,7 +148,7 @@ namespace osu.Game.Graphics.UserInterface protected override void LoadComplete() { base.LoadComplete(); - CurrentNumber.BindValueChanged(current => TooltipText = GetTooltipText(current.NewValue), true); + CurrentNumber.BindValueChanged(current => TooltipText = getTooltipText(current.NewValue), true); } protected override bool OnHover(HoverEvent e) @@ -178,7 +178,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnUserChange(value); playSample(value); - TooltipText = GetTooltipText(value); + TooltipText = getTooltipText(value); } private void playSample(T value) @@ -203,7 +203,7 @@ namespace osu.Game.Graphics.UserInterface channel.Play(); } - protected virtual LocalisableString GetTooltipText(T value) + private LocalisableString getTooltipText(T value) { if (CurrentNumber.IsInteger) return value.ToInt32(NumberFormatInfo.InvariantInfo).ToString("N0"); diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 2e14480e2b..bb8dcf566d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -97,12 +97,10 @@ namespace osu.Game.Screens.Play.PlayerSettings protected class CustomSliderBar : SliderBar { - protected override LocalisableString GetTooltipText(double value) - { - return value == 0 - ? new TranslatableString("_", @"{0} ms", base.GetTooltipText(value)) - : new TranslatableString("_", @"{0} ms {1}", base.GetTooltipText(value), getEarlyLateText(value)); - } + public override LocalisableString TooltipText => + Current.Value == 0 + ? new TranslatableString("_", @"{0} ms", base.TooltipText) + : new TranslatableString("_", @"{0} ms {1}", base.TooltipText, getEarlyLateText(Current.Value)); private LocalisableString getEarlyLateText(double value) { From 08b3bc222d79a8d2a846ee11c1e300832d91a9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 5 Mar 2022 16:42:51 +0100 Subject: [PATCH 64/78] Revert "Fix potential crash in tests when attempting to lookup key bindings in cases the lookup is not available" This reverts commit 8115a4bb8fd9e2d53c40b8607c7ad99f0f62e9a0. Commit was cherrypicked out to a separate pull on a different merge base, then reverted in that pull, so it should be reverted here too. --- osu.Game/Configuration/DevelopmentOsuConfigManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs index f1cb0731fe..ff19dd874c 100644 --- a/osu.Game/Configuration/DevelopmentOsuConfigManager.cs +++ b/osu.Game/Configuration/DevelopmentOsuConfigManager.cs @@ -14,8 +14,6 @@ namespace osu.Game.Configuration public DevelopmentOsuConfigManager(Storage storage) : base(storage) { - LookupKeyBindings = _ => "unknown"; - LookupSkinName = _ => "unknown"; } } } From f8ef352306a133d935bd9c441a6419de62a5d825 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:00:47 +0900 Subject: [PATCH 65/78] Don't consider judgements beneath the minimum height as being applicable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index bb42dda597..61b0cff8dd 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -250,7 +250,7 @@ namespace osu.Game.Screens.Ranking.Statistics public void UpdateOffset(float adjustment) { - bool hasAdjustment = adjustment != value; + bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height; if (boxAdjustment == null) { From 06512e8bd90d96ee62daa1866841e73499608505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:01:22 +0900 Subject: [PATCH 66/78] Use `const` for minimum height specification in final usage location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 61b0cff8dd..f7c9d36cc4 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -269,7 +269,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, 0.05f, 1), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From 0e8ad4b143785b571206729846856a1512c2e4e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 6 Mar 2022 01:50:25 +0900 Subject: [PATCH 67/78] Switch step to `Until` steps due to `AddOnce` firing logic --- .../Visual/Gameplay/TestSceneBeatmapOffsetControl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index 4b079cbb2c..8ca49837da 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -62,11 +62,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } @@ -90,11 +90,11 @@ namespace osu.Game.Tests.Visual.Gameplay }; }); - AddAssert("Has calibration button", () => offsetControl.ChildrenOfType().Any()); + AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType().Any()); AddStep("Press button", () => offsetControl.ChildrenOfType().Single().TriggerClick()); AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error); - AddAssert("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); + AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType().Single().Enabled.Value); AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null); AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); } From 5b3ffb12b7c978f4ec1a4c8de8f346b854a5b9d3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:23:58 +0300 Subject: [PATCH 68/78] Refactor channel scrolling container to handle manual scrolls resiliently --- .../Online/TestSceneStandAloneChatDisplay.cs | 27 ++++++++++++++++--- .../Containers/UserTrackingScrollContainer.cs | 21 ++++++++++----- osu.Game/Overlays/Chat/DrawableChannel.cs | 19 ++++++------- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 779d72190d..9b9ee0e084 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -207,7 +207,28 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestUserScrollOverride() + public void TestOverrideChatScrolling() + { + fillChat(); + + sendMessage(); + checkScrolledToBottom(); + + AddStep("Scroll to start", () => chatDisplay.ScrollContainer.ScrollToStart()); + + checkNotScrolledToBottom(); + sendMessage(); + checkNotScrolledToBottom(); + + AddStep("Scroll to bottom", () => chatDisplay.ScrollContainer.ScrollToEnd()); + + checkScrolledToBottom(); + sendMessage(); + checkScrolledToBottom(); + } + + [Test] + public void TestOverrideChatScrollingByUser() { fillChat(); @@ -314,9 +335,9 @@ namespace osu.Game.Tests.Visual.Online { } - protected DrawableChannel DrawableChannel => InternalChildren.OfType().First(); + public DrawableChannel DrawableChannel => InternalChildren.OfType().First(); - protected UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; + public UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 0561051e35..988695c380 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } - public void CancelUserScroll() => UserScrolling = false; - public UserTrackingScrollContainer() { } @@ -38,26 +36,37 @@ namespace osu.Game.Graphics.Containers protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { - UserScrolling = true; base.OnUserScroll(value, animated, distanceDecay); + OnScrollChange(true); } public new void ScrollIntoView(Drawable target, bool animated = true) { - UserScrolling = false; base.ScrollIntoView(target, animated); + OnScrollChange(false); } public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) { - UserScrolling = false; base.ScrollTo(value, animated, distanceDecay); + OnScrollChange(false); + } + + public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToStart(animated, allowDuringDrag); + OnScrollChange(false); } public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) { - UserScrolling = false; base.ScrollToEnd(animated, allowDuringDrag); + OnScrollChange(false); } + + /// + /// Invoked when any scroll has been performed either automatically or by user. + /// + protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 41e70bbfae..ccad55b809 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -249,31 +249,32 @@ namespace osu.Game.Overlays.Chat /// private const float auto_scroll_leniency = 10f; + private bool trackNewContent = true; private float? lastExtent; - protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) + protected override void OnScrollChange(bool byUser) { - base.OnUserScroll(value, animated, distanceDecay); - lastExtent = null; + base.OnScrollChange(byUser); + + if (byUser) + lastExtent = null; + + trackNewContent = IsScrolledToEnd(auto_scroll_leniency); } protected override void Update() { base.Update(); - // If the user has scrolled to the bottom of the container, we should resume tracking new content. - if (UserScrolling && IsScrolledToEnd(auto_scroll_leniency)) - CancelUserScroll(); - // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = !UserScrolling && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); + bool requiresScrollUpdate = trackNewContent && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); if (requiresScrollUpdate) { // Schedule required to allow FillFlow to be the correct size. Schedule(() => { - if (!UserScrolling) + if (trackNewContent) { if (Current < ScrollableExtent) ScrollToEnd(); From 634821e49f4485882539534e6e1df7fdb0259f1f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 00:01:45 +0300 Subject: [PATCH 69/78] Use the term "programmatically" instead --- osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 988695c380..887aeb0c54 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -65,7 +65,7 @@ namespace osu.Game.Graphics.Containers } /// - /// Invoked when any scroll has been performed either automatically or by user. + /// Invoked when any scroll has been performed either programmatically or by user. /// protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } From e1eeb9c6bbaead86f6ce482a69700d6e3ec2144a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Mar 2022 01:43:56 +0100 Subject: [PATCH 70/78] Allow tabbing between textboxes in sample point popover --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 +- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 7 ++++++- .../Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 4da8d6a554..fd64cc2056 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.Text = value; } - public Container TabbableContentContainer + public CompositeDrawable TabbableContentContainer { set => Component.TabbableContentContainer = value; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 7d52645aa1..fc0952d4f0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -75,9 +75,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [BackgroundDependencyLoader] private void load() { + FillFlowContainer flow; + Children = new Drawable[] { - new FillFlowContainer + flow = new FillFlowContainer { Width = 200, Direction = FillDirection.Vertical, @@ -94,6 +96,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; + bank.TabbableContentContainer = flow; + volume.TabbableContentContainer = flow; + // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index e25d83cfb0..0cf2cf6c54 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Timing set => slider.KeyboardStep = value; } + public CompositeDrawable TabbableContentContainer + { + set => textBox.TabbableContentContainer = value; + } + private readonly BindableWithCurrent current = new BindableWithCurrent(); public Bindable Current From df0617f34c33a31050c94cd113dd94819d064ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 15:03:21 +0100 Subject: [PATCH 71/78] Implement popup screen title component --- .../TestScenePopupScreenTitle.cs | 44 +++++ .../UserInterface/PopupScreenTitle.cs | 151 ++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs create mode 100644 osu.Game/Graphics/UserInterface/PopupScreenTitle.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs new file mode 100644 index 0000000000..c214c158a4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs @@ -0,0 +1,44 @@ +// 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.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestScenePopupScreenTitle : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestPopupScreenTitle() + { + AddStep("create content", () => + { + Child = new PopupScreenTitle + { + Title = "Popup Screen Title", + Description = "This is a description.", + Close = () => { } + }; + }); + } + + [Test] + public void TestDisabledExit() + { + AddStep("create content", () => + { + Child = new PopupScreenTitle + { + Title = "Popup Screen Title", + Description = "This is a description." + }; + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs new file mode 100644 index 0000000000..8d5aef7427 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs @@ -0,0 +1,151 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class PopupScreenTitle : CompositeDrawable + { + public LocalisableString Title + { + get => titleSpriteText.Text; + set => titleSpriteText.Text = value; + } + + public LocalisableString Description + { + get => descriptionSpriteText.Text; + set => descriptionSpriteText.Text = value; + } + + public Action? Close + { + get => closeButton.Action; + set => closeButton.Action = value; + } + + private const float corner_radius = 14; + private const float main_area_height = 70; + + private readonly Container underlayContainer; + private readonly Box underlayBackground; + private readonly Container contentContainer; + private readonly Box contentBackground; + private readonly OsuSpriteText titleSpriteText; + private readonly OsuSpriteText descriptionSpriteText; + private readonly IconButton closeButton; + + public PopupScreenTitle() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 70, + Top = -corner_radius + }, + Children = new Drawable[] + { + underlayContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = main_area_height + 2 * corner_radius, + CornerRadius = corner_radius, + Masking = true, + BorderThickness = 2, + Child = underlayBackground = new Box + { + RelativeSizeAxes = Axes.Both + } + }, + contentContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = main_area_height + corner_radius, + CornerRadius = corner_radius, + Masking = true, + BorderThickness = 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Colour4.Black.Opacity(0.1f), + Offset = new Vector2(0, 1), + Radius = 3 + }, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = corner_radius }, + Padding = new MarginPadding { Horizontal = 100 }, + Children = new Drawable[] + { + titleSpriteText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 20) + }, + descriptionSpriteText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 12) + } + } + }, + closeButton = new IconButton + { + Icon = FontAwesome.Solid.Times, + Scale = new Vector2(0.6f), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding + { + Right = 21, + Top = corner_radius + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + underlayContainer.BorderColour = ColourInfo.GradientVertical(Colour4.Black, colourProvider.Dark4); + underlayBackground.Colour = colourProvider.Dark4; + + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Dark3, colourProvider.Dark1); + contentBackground.Colour = colourProvider.Dark3; + + closeButton.IconHoverColour = colourProvider.Highlight1; + } + } +} From 54275813b57e88d8709d1fbf1c3e33d329ed7cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Feb 2022 18:10:30 +0100 Subject: [PATCH 72/78] Use text flow container in popup screen title --- .../UserInterface/TestScenePopupScreenTitle.cs | 3 ++- .../Graphics/UserInterface/PopupScreenTitle.cs | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs index c214c158a4..22a8fa8a46 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupScreenTitle.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface Child = new PopupScreenTitle { Title = "Popup Screen Title", - Description = "This is a description.", + Description = string.Join(" ", Enumerable.Repeat("This is a description.", 20)), Close = () => { } }; }); diff --git a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs index 8d5aef7427..5b7db09e77 100644 --- a/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs +++ b/osu.Game/Graphics/UserInterface/PopupScreenTitle.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; @@ -22,14 +23,12 @@ namespace osu.Game.Graphics.UserInterface { public LocalisableString Title { - get => titleSpriteText.Text; set => titleSpriteText.Text = value; } public LocalisableString Description { - get => descriptionSpriteText.Text; - set => descriptionSpriteText.Text = value; + set => descriptionText.Text = value; } public Action? Close @@ -46,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Container contentContainer; private readonly Box contentBackground; private readonly OsuSpriteText titleSpriteText; - private readonly OsuSpriteText descriptionSpriteText; + private readonly OsuTextFlowContainer descriptionText; private readonly IconButton closeButton; public PopupScreenTitle() @@ -112,9 +111,13 @@ namespace osu.Game.Graphics.UserInterface { Font = OsuFont.TorusAlternate.With(size: 20) }, - descriptionSpriteText = new OsuSpriteText + descriptionText = new OsuTextFlowContainer(t => { - Font = OsuFont.Default.With(size: 12) + t.Font = OsuFont.Default.With(size: 12); + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y } } }, From 9bc1f3f014178f19c5c174ba60bcff8d617762d7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:34:12 +0300 Subject: [PATCH 73/78] Further refactor and simplify `ChannelScrollContainer` --- .../Online/TestSceneStandAloneChatDisplay.cs | 3 +- .../Overlays/Chat/ChannelScrollContainer.cs | 78 +++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 50 ------------ 3 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 osu.Game/Overlays/Chat/ChannelScrollContainer.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 9b9ee0e084..a21647712d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,7 +9,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -337,7 +336,7 @@ namespace osu.Game.Tests.Visual.Online public DrawableChannel DrawableChannel => InternalChildren.OfType().First(); - public UserTrackingScrollContainer ScrollContainer => (UserTrackingScrollContainer)((Container)DrawableChannel.Child).Child; + public ChannelScrollContainer ScrollContainer => (ChannelScrollContainer)((Container)DrawableChannel.Child).Child; public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child; diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs new file mode 100644 index 0000000000..23bc683661 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Chat +{ + /// + /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. + /// + public class ChannelScrollContainer : OsuScrollContainer + { + /// + /// The chat will be automatically scrolled to end if and only if + /// the distance between the current scroll position and the end of the scroll + /// is less than this value. + /// + private const float auto_scroll_leniency = 10f; + + private bool trackNewContent = true; + + protected override void Update() + { + base.Update(); + + // If our behaviour hasn't been overriden and there has been new content added to the container, we should update our scroll position to track it. + bool requiresScrollUpdate = trackNewContent && !IsScrolledToEnd(); + + if (requiresScrollUpdate) + { + // Schedule required to allow FillFlow to be the correct size. + Schedule(() => + { + if (trackNewContent) + { + if (Current < ScrollableExtent) + ScrollToEnd(); + } + }); + } + } + + private void updateTrackState() => trackNewContent = IsScrolledToEnd(auto_scroll_leniency); + + // todo: we may eventually want this encapsulated in a "OnScrollChange" event handler method provided by ScrollContainer. + // important to note that this intentionally doesn't consider OffsetScrollPosition, but could make it do so with side changes. + + #region Scroll handling + + protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = null) + { + base.OnUserScroll(value, animated, distanceDecay); + updateTrackState(); + } + + public new void ScrollIntoView(Drawable d, bool animated = true) + { + base.ScrollIntoView(d, animated); + updateTrackState(); + } + + public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToStart(animated, allowDuringDrag); + updateTrackState(); + } + + public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) + { + base.ScrollToEnd(animated, allowDuringDrag); + updateTrackState(); + } + + #endregion + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index ccad55b809..6220beeb82 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -11,9 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -236,53 +234,5 @@ namespace osu.Game.Overlays.Chat }; } } - - /// - /// An with functionality to automatically scroll whenever the maximum scrollable distance increases. - /// - private class ChannelScrollContainer : UserTrackingScrollContainer - { - /// - /// The chat will be automatically scrolled to end if and only if - /// the distance between the current scroll position and the end of the scroll - /// is less than this value. - /// - private const float auto_scroll_leniency = 10f; - - private bool trackNewContent = true; - private float? lastExtent; - - protected override void OnScrollChange(bool byUser) - { - base.OnScrollChange(byUser); - - if (byUser) - lastExtent = null; - - trackNewContent = IsScrolledToEnd(auto_scroll_leniency); - } - - protected override void Update() - { - base.Update(); - - // If the user hasn't overridden our behaviour and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = trackNewContent && (lastExtent == null || Precision.AlmostBigger(ScrollableExtent, lastExtent.Value)); - - if (requiresScrollUpdate) - { - // Schedule required to allow FillFlow to be the correct size. - Schedule(() => - { - if (trackNewContent) - { - if (Current < ScrollableExtent) - ScrollToEnd(); - lastExtent = ScrollableExtent; - } - }); - } - } - } } } From 9ec0e7481332cc69e018f7c4ca6d60059cb4cd42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:50:57 +0300 Subject: [PATCH 74/78] Move scrolling to `UpdateAfterChildren` to avoid scheduling At least that's what I believe "let FillFlow update to new size" means. --- .../Overlays/Chat/ChannelScrollContainer.cs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 23bc683661..1d1d35dc34 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Chat @@ -21,25 +20,12 @@ namespace osu.Game.Overlays.Chat private bool trackNewContent = true; - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); - // If our behaviour hasn't been overriden and there has been new content added to the container, we should update our scroll position to track it. - bool requiresScrollUpdate = trackNewContent && !IsScrolledToEnd(); - - if (requiresScrollUpdate) - { - // Schedule required to allow FillFlow to be the correct size. - Schedule(() => - { - if (trackNewContent) - { - if (Current < ScrollableExtent) - ScrollToEnd(); - } - }); - } + if (trackNewContent && !IsScrolledToEnd()) + ScrollToEnd(); } private void updateTrackState() => trackNewContent = IsScrolledToEnd(auto_scroll_leniency); From a13a087f5dda13342fe9707a63d4421dbbdcdfb0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:51:27 +0300 Subject: [PATCH 75/78] Add xmldoc to `trackNewContent` to explain its purpose --- osu.Game/Overlays/Chat/ChannelScrollContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 1d1d35dc34..58b2b9a075 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -18,6 +18,12 @@ namespace osu.Game.Overlays.Chat /// private const float auto_scroll_leniency = 10f; + /// + /// Whether to keep this container scrolled to end on new content. + /// + /// + /// This is specifically controlled by whether the latest scroll operation made the container scrolled to end. + /// private bool trackNewContent = true; protected override void UpdateAfterChildren() From 60334046e4dd7dface21e505d9ea929627993433 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:57:51 +0300 Subject: [PATCH 76/78] Revert `UserTrackingScrollContainer` changes --- .../Containers/UserTrackingScrollContainer.cs | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 887aeb0c54..0561051e35 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,6 +25,8 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } + public void CancelUserScroll() => UserScrolling = false; + public UserTrackingScrollContainer() { } @@ -36,37 +38,26 @@ namespace osu.Game.Graphics.Containers protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { + UserScrolling = true; base.OnUserScroll(value, animated, distanceDecay); - OnScrollChange(true); } public new void ScrollIntoView(Drawable target, bool animated = true) { + UserScrolling = false; base.ScrollIntoView(target, animated); - OnScrollChange(false); } public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) { + UserScrolling = false; base.ScrollTo(value, animated, distanceDecay); - OnScrollChange(false); - } - - public new void ScrollToStart(bool animated = true, bool allowDuringDrag = false) - { - base.ScrollToStart(animated, allowDuringDrag); - OnScrollChange(false); } public new void ScrollToEnd(bool animated = true, bool allowDuringDrag = false) { + UserScrolling = false; base.ScrollToEnd(animated, allowDuringDrag); - OnScrollChange(false); } - - /// - /// Invoked when any scroll has been performed either programmatically or by user. - /// - protected virtual void OnScrollChange(bool byUser) => UserScrolling = byUser; } } From d9be65ea39fc20af1f991f99940539da37b5e815 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 6 Mar 2022 23:58:06 +0300 Subject: [PATCH 77/78] Remove no longer necessary `CancelUserScroll` method --- osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs index 0561051e35..44afaf77ea 100644 --- a/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs +++ b/osu.Game/Graphics/Containers/UserTrackingScrollContainer.cs @@ -25,8 +25,6 @@ namespace osu.Game.Graphics.Containers /// public bool UserScrolling { get; private set; } - public void CancelUserScroll() => UserScrolling = false; - public UserTrackingScrollContainer() { } From da29947ecd36fc3479d6e82ee8ca45aba4c1338a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Mar 2022 11:34:06 +0900 Subject: [PATCH 78/78] Disallow interaction with carousel set difficulty icons unless selected I kinda liked this flow, but from multiple reports from users it definitely seems in the way. We can revisit after the new design is applied to song select. Note that this means the tooltips also don't display. If it is preferred that they should (arguable from a UX perspective, since I'd expect to be able to click at that point) then the issue can be addressed using a slightly different path (a few more lines - nothing too complex). --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 760915b528..a000cfd5fc 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -16,6 +16,9 @@ namespace osu.Game.Screens.Select.Carousel { public class SetPanelContent : CompositeDrawable { + // Disallow interacting with difficulty icons on a panel until the panel has been selected. + public override bool PropagatePositionalInputSubTree => carouselSet.State.Value == CarouselItemState.Selected; + private readonly CarouselBeatmapSet carouselSet; public SetPanelContent(CarouselBeatmapSet carouselSet)