From c9de6a383d71829eb3ec23c4a7571b72480459c1 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:21:54 +0100 Subject: [PATCH 01/27] Add settings checkbox for `MinimiseOnFocusLossInFullscreen` Shown only on desktop platforms in fullscreen. "alt-tab" keyword also matches "alt" and "tab". --- osu.Game/Localisation/GraphicsSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 422704514f..fc29352ae2 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -155,6 +155,11 @@ namespace osu.Game.Localisation public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); + /// + /// "Minimise on focus loss" + /// + public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise on focus loss"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a3290bc81c..adb572e245 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -51,6 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private SettingsDropdown resolutionDropdown = null!; private SettingsDropdown displayDropdown = null!; private SettingsDropdown windowModeDropdown = null!; + private SettingsCheckbox minimiseOnFocusLossCheckbox = null!; private SettingsCheckbox safeAreaConsiderationsCheckbox = null!; private Bindable scalingPositionX = null!; @@ -106,6 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics ItemSource = resolutions, Current = sizeFullscreen }, + minimiseOnFocusLossCheckbox = new SettingsCheckbox + { + LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss, + Current = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen), + Keywords = new[] { "alt-tab" }, + }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { LabelText = "Shrink game to avoid cameras and notches", @@ -255,6 +262,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen; displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1; + minimiseOnFocusLossCheckbox.CanBeShown.Value = RuntimeInfo.IsDesktop && windowModeDropdown.Current.Value == WindowMode.Fullscreen; safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero; } From 0fde9cd6ae676c3bef39cd34182fbe0cfe338526 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:40:52 +0100 Subject: [PATCH 02/27] Override confine mouse mode only when clicking outside the window would minimise it --- osu.Game/Input/ConfineMouseTracker.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/ConfineMouseTracker.cs b/osu.Game/Input/ConfineMouseTracker.cs index de8660dbce..926f68df45 100644 --- a/osu.Game/Input/ConfineMouseTracker.cs +++ b/osu.Game/Input/ConfineMouseTracker.cs @@ -22,6 +22,7 @@ namespace osu.Game.Input { private Bindable frameworkConfineMode; private Bindable frameworkWindowMode; + private Bindable frameworkMinimiseOnFocusLossInFullscreen; private Bindable osuConfineMode; private IBindable localUserPlaying; @@ -31,7 +32,9 @@ namespace osu.Game.Input { frameworkConfineMode = frameworkConfigManager.GetBindable(FrameworkSetting.ConfineMouseMode); frameworkWindowMode = frameworkConfigManager.GetBindable(FrameworkSetting.WindowMode); + frameworkMinimiseOnFocusLossInFullscreen = frameworkConfigManager.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen); frameworkWindowMode.BindValueChanged(_ => updateConfineMode()); + frameworkMinimiseOnFocusLossInFullscreen.BindValueChanged(_ => updateConfineMode()); osuConfineMode = osuConfigManager.GetBindable(OsuSetting.ConfineMouseMode); localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); @@ -46,7 +49,8 @@ namespace osu.Game.Input if (frameworkConfineMode.Disabled) return; - if (frameworkWindowMode.Value == WindowMode.Fullscreen) + // override confine mode only when clicking outside the window minimises it. + if (frameworkWindowMode.Value == WindowMode.Fullscreen && frameworkMinimiseOnFocusLossInFullscreen.Value) { frameworkConfineMode.Value = ConfineMouseMode.Fullscreen; return; From a6bed04e50b6bcc7845ca85157bb2c6672c991d5 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Wed, 27 Dec 2023 16:54:01 +0100 Subject: [PATCH 03/27] Update confine mode settings checkbox to match the new `ConfineMouseTracker` logic Not too happy with the duplicated logic, but settings can't depend on `ConfineMouseTracker` for testability reasons. --- .../Settings/Sections/Input/MouseSettings.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 6bf06f4f98..7805ed5834 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Bindable localSensitivity; private Bindable windowMode; + private Bindable minimiseOnFocusLoss; private SettingsEnumDropdown confineMouseModeSetting; private Bindable relativeMode; @@ -47,6 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy(); windowMode = config.GetBindable(FrameworkSetting.WindowMode); + minimiseOnFocusLoss = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen); Children = new Drawable[] { @@ -98,21 +100,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue); - windowMode.BindValueChanged(mode => - { - bool isFullscreen = mode.NewValue == WindowMode.Fullscreen; - - if (isFullscreen) - { - confineMouseModeSetting.Current.Disabled = true; - confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; - } - else - { - confineMouseModeSetting.Current.Disabled = false; - confineMouseModeSetting.TooltipText = string.Empty; - } - }, true); + windowMode.BindValueChanged(_ => updateConfineMouseModeSettingVisibility()); + minimiseOnFocusLoss.BindValueChanged(_ => updateConfineMouseModeSettingVisibility(), true); highPrecisionMouse.Current.BindValueChanged(highPrecision => { @@ -126,6 +115,25 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + /// + /// Updates disabled state and tooltip of to match when is overriding the confine mode. + /// + private void updateConfineMouseModeSettingVisibility() + { + bool confineModeOverriden = windowMode.Value == WindowMode.Fullscreen && minimiseOnFocusLoss.Value; + + if (confineModeOverriden) + { + confineMouseModeSetting.Current.Disabled = true; + confineMouseModeSetting.TooltipText = MouseSettingsStrings.NotApplicableFullscreen; + } + else + { + confineMouseModeSetting.Current.Disabled = false; + confineMouseModeSetting.TooltipText = string.Empty; + } + } + public partial class SensitivitySetting : SettingsSlider { public SensitivitySetting() From d34f30f6ad1a2c5071eddb2ed1763465be3cc70b Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 14:37:57 +0600 Subject: [PATCH 04/27] Add `Statistics` bindable to `IAPIProvider` and update it from `SoloStatisticsWatcher` --- osu.Game/Online/API/APIAccess.cs | 14 +++++++++++++- osu.Game/Online/API/DummyAPIAccess.cs | 14 ++++++++++++++ osu.Game/Online/API/IAPIProvider.cs | 10 ++++++++++ osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 ++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index be5bdeca77..3910f1efea 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -53,6 +53,7 @@ namespace osu.Game.Online.API public IBindable LocalUser => localUser; public IBindableList Friends => friends; public IBindable Activity => activity; + public IBindable Statistics => statistics; public Language Language => game.CurrentLanguage.Value; @@ -65,6 +66,8 @@ namespace osu.Game.Online.API private Bindable configStatus { get; } = new Bindable(); private Bindable localUserStatus { get; } = new Bindable(); + private Bindable statistics { get; } = new Bindable(); + protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -517,9 +520,18 @@ namespace osu.Game.Online.API flushQueue(); } + public void UpdateStatistics(UserStatistics newStatistics) + { + statistics.Value = newStatistics; + } + private static APIUser createGuestUser() => new GuestUser(); - private void setLocalUser(APIUser user) => Scheduler.Add(() => localUser.Value = user, false); + private void setLocalUser(APIUser user) => Scheduler.Add(() => + { + localUser.Value = user; + statistics.Value = user.Statistics; + }, false); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index d585124db6..aa8658cc83 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -28,6 +28,8 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); + public Bindable Statistics { get; } = new Bindable(); + public Language Language => Language.en; public string AccessToken => "token"; @@ -115,6 +117,12 @@ namespace osu.Game.Online.API Id = DUMMY_USER_ID, }; + Statistics.Value = new UserStatistics + { + GlobalRank = 1, + CountryRank = 1 + }; + state.Value = APIState.Online; } @@ -126,6 +134,11 @@ namespace osu.Game.Online.API LocalUser.Value = new GuestUser(); } + public void UpdateStatistics(UserStatistics newStatistics) + { + Statistics.Value = newStatistics; + } + public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; public NotificationsClientConnector GetNotificationsConnector() => new PollingNotificationsClientConnector(this); @@ -141,6 +154,7 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; + IBindable IAPIProvider.Statistics => Statistics; /// /// During the next simulated login, the process will fail immediately. diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a1d7006c8c..b58d4a363a 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -28,6 +28,11 @@ namespace osu.Game.Online.API /// IBindable Activity { get; } + /// + /// The current user's online statistics. + /// + IBindable Statistics { get; } + /// /// The language supplied by this provider to API requests. /// @@ -111,6 +116,11 @@ namespace osu.Game.Online.API /// void Logout(); + /// + /// Sets Statistics bindable. + /// + void UpdateStatistics(UserStatistics newStatistics); + /// /// Constructs a new . May be null if not supported. /// diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 46449fea73..55b27fb364 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -127,6 +127,8 @@ namespace osu.Game.Online.Solo { string rulesetName = callback.Score.Ruleset.ShortName; + api.UpdateStatistics(updatedStatistics); + if (latestStatistics == null) return; From e240443c467436a470c12fd77b3035b7720a8cdd Mon Sep 17 00:00:00 2001 From: StanR Date: Wed, 3 Jan 2024 18:15:32 +0600 Subject: [PATCH 05/27] Update `LocalUser` statistics, add test --- .../Online/TestSceneSoloStatisticsWatcher.cs | 20 +++++++++++++++++++ osu.Game/Online/API/APIAccess.cs | 3 +++ osu.Game/Online/API/DummyAPIAccess.cs | 3 +++ 3 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index e62e53bd02..be819afa3e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -268,6 +268,26 @@ namespace osu.Game.Tests.Visual.Online AddAssert("update not received", () => update == null); } + [Test] + public void TestGlobalStatisticsUpdatedAfterRegistrationAddedAndScoreProcessed() + { + int userId = getUserId(); + long scoreId = getScoreId(); + setUpUser(userId); + + var ruleset = new OsuRuleset().RulesetInfo; + + SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); + AddUntilStep("update received", () => update != null); + AddAssert("local user values are correct", () => dummyAPI.LocalUser.Value.Statistics.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("statistics values are correct", () => dummyAPI.Statistics.Value!.TotalScore, () => Is.EqualTo(5_000_000)); + } + private int nextUserId = 2000; private long nextScoreId = 50000; diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 3910f1efea..17bf8bcc37 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -523,6 +523,9 @@ namespace osu.Game.Online.API public void UpdateStatistics(UserStatistics newStatistics) { statistics.Value = newStatistics; + + if (IsLoggedIn) + localUser.Value.Statistics = newStatistics; } private static APIUser createGuestUser() => new GuestUser(); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index aa8658cc83..4b4f8061e0 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -137,6 +137,9 @@ namespace osu.Game.Online.API public void UpdateStatistics(UserStatistics newStatistics) { Statistics.Value = newStatistics; + + if (IsLoggedIn) + LocalUser.Value.Statistics = newStatistics; } public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null; From 5303023e574c9e32239bd5afe4bc311241e07faa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:41:53 +0300 Subject: [PATCH 06/27] Add failing test case and fix selection assertion --- .../TestSceneFreeModSelectOverlay.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index f1674401cd..a4feffddfb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -10,12 +10,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Testing; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; +using osu.Game.Utils; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene { private FreeModSelectOverlay freeModSelectOverlay; + private FooterButtonFreeMods footerButtonFreeMods; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -119,11 +122,46 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } + [Test] + public void TestSelectAllViaFooterButtonThenDeselectFromOverlay() + { + createFreeModSelect(); + + AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType().Single().Enabled.Value); + AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); + + AddStep("click footer select all button", () => + { + InputManager.MoveMouseTo(footerButtonFreeMods); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("all mods selected", assertAllAvailableModsSelected); + AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "all")); + + AddStep("click deselect all button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); + AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); + } + private void createFreeModSelect() { - AddStep("create free mod select screen", () => Child = freeModSelectOverlay = new FreeModSelectOverlay + AddStep("create free mod select screen", () => Children = new Drawable[] { - State = { Value = Visibility.Visible } + freeModSelectOverlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }, + footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + }, }); AddUntilStep("all column content loaded", () => freeModSelectOverlay.ChildrenOfType().Any() @@ -134,10 +172,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { var allAvailableMods = availableMods.Value .Where(pair => pair.Key != ModType.System) - .SelectMany(pair => pair.Value) + .SelectMany(pair => ModUtils.FlattenMods(pair.Value)) .Where(mod => mod.UserPlayable && mod.HasImplementation) .ToList(); + if (freeModSelectOverlay.SelectedMods.Value.Count != allAvailableMods.Count) + return false; + foreach (var availableMod in allAvailableMods) { if (freeModSelectOverlay.SelectedMods.Value.All(selectedMod => selectedMod.GetType() != availableMod.GetType())) From c476843a837882bd6d3eb7164a20203a9e6d1774 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:43:20 +0300 Subject: [PATCH 07/27] Mark system mods as invalid for selection in mod select overlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index baa7e594c1..f9875c5547 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -447,7 +447,7 @@ namespace osu.Game.Overlays.Mods private void filterMods() { foreach (var modState in AllAvailableMods) - modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); + modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } private void updateMultiplier() From aebf246f627b8658e94654d903c8d05bd23a190d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jan 2024 09:43:50 +0300 Subject: [PATCH 08/27] Change select all mod buttons to check `ValidForSelection` instead of directly checking system mods --- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index b6b3051a0d..1da762d164 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -41,8 +41,8 @@ namespace osu.Game.Overlays.Mods private void updateEnabledState() { Enabled.Value = availableMods.Value - .Where(pair => pair.Key != ModType.System) .SelectMany(pair => pair.Value) + .Where(modState => modState.ValidForSelection.Value) .Any(modState => !modState.Active.Value && modState.Visible); } } From f5d6d52d4c9160affc348e4dd81cbb9a1f5ed352 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 14:47:40 +0100 Subject: [PATCH 09/27] Move logic for caching segments and updating path types to PathControlPointVisualiser --- .../Components/PathControlPointPiece.cs | 42 ---------------- .../Components/PathControlPointVisualiser.cs | 48 +++++++++++++++++++ 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 03792d8520..9b40b39a9d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -5,19 +5,15 @@ using System; using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -56,27 +52,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IBindable hitObjectPosition; private IBindable hitObjectScale; - [UsedImplicitly] - private readonly IBindable hitObjectVersion; - public PathControlPointPiece(T hitObject, PathControlPoint controlPoint) { this.hitObject = hitObject; ControlPoint = controlPoint; - // we don't want to run the path type update on construction as it may inadvertently change the hit object. - cachePoints(hitObject); - - hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); - - // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the hit object path type for batch operations. - hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() => - { - cachePoints(hitObject); - updatePathType(); - })); - controlPoint.Changed += updateMarkerDisplay; Origin = Anchor.Centre; @@ -214,28 +194,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke(); - private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint); - - /// - /// Handles correction of invalid path types. - /// - private void updatePathType() - { - if (ControlPoint.Type != PathType.PERFECT_CURVE) - return; - - if (PointsInSegment.Count > 3) - ControlPoint.Type = PathType.BEZIER; - - if (PointsInSegment.Count != 3) - return; - - ReadOnlySpan points = PointsInSegment.Select(p => p.Position).ToArray(); - RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); - if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - ControlPoint.Type = PathType.BEZIER; - } - /// /// Updates the state of the circular control point marker. /// diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24e2210b45..aae6275d45 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -9,15 +9,18 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using Humanizer; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -52,6 +55,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } + [UsedImplicitly] + private readonly IBindable hitObjectVersion; + public PathControlPointVisualiser(T hitObject, bool allowSelection) { this.hitObject = hitObject; @@ -64,6 +70,48 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Connections = new Container> { RelativeSizeAxes = Axes.Both }, Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; + + hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); + + // schedule ensure that updates are only applied after all operations from a single frame are applied. + // this avoids inadvertently changing the hit object path type for batch operations. + hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(cachePointsAndEnsureValidPathTypes)); + } + + /// + /// Caches the PointsInSegment of Pieces and handles correction of invalid path types. + /// + private void cachePointsAndEnsureValidPathTypes() + { + List pointsInCurrentSegment = new List(); + + foreach (var controlPoint in controlPoints) + { + if (controlPoint.Type != null) + pointsInCurrentSegment = new List(); + + pointsInCurrentSegment.Add(controlPoint); + + // Pieces might not be ordered so we need to find the piece corresponding to the current control point. + Pieces.Single(o => o.ControlPoint == controlPoint).PointsInSegment = pointsInCurrentSegment; + } + + foreach (var piece in Pieces) + { + if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) + return; + + if (piece.PointsInSegment.Count > 3) + piece.ControlPoint.Type = PathType.BEZIER; + + if (piece.PointsInSegment.Count != 3) + return; + + ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + piece.ControlPoint.Type = PathType.BEZIER; + } } protected override void LoadComplete() From b4f9878b4656e83da770fcd00f1b70dd109e4a84 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 20:39:49 +0100 Subject: [PATCH 10/27] working jank solution --- .../Components/PathControlPointVisualiser.cs | 36 ++++++++++++------- osu.Game/Rulesets/Objects/SliderPath.cs | 4 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index aae6275d45..a02a07f2b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -72,10 +72,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components }; hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + + controlPoints.CollectionChanged += onControlPointsChanged; + controlPoints.BindTo(hitObject.Path.ControlPoints); // schedule ensure that updates are only applied after all operations from a single frame are applied. // this avoids inadvertently changing the hit object path type for batch operations. - hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(cachePointsAndEnsureValidPathTypes)); + hitObject.Path.Validating += cachePointsAndEnsureValidPathTypes; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + hitObject.Path.Validating -= cachePointsAndEnsureValidPathTypes; } /// @@ -88,7 +105,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var controlPoint in controlPoints) { if (controlPoint.Type != null) + { + pointsInCurrentSegment.Add(controlPoint); pointsInCurrentSegment = new List(); + } pointsInCurrentSegment.Add(controlPoint); @@ -99,13 +119,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components foreach (var piece in Pieces) { if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) - return; + continue; if (piece.PointsInSegment.Count > 3) piece.ControlPoint.Type = PathType.BEZIER; if (piece.PointsInSegment.Count != 3) - return; + continue; ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); @@ -114,16 +134,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - - controlPoints.CollectionChanged += onControlPointsChanged; - controlPoints.BindTo(hitObject.Path.ControlPoints); - } - /// /// Selects the corresponding to the given , /// and deselects all other s. diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index dc71608132..bafc62ceda 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Objects private readonly Bindable version = new Bindable(); + public event Action? Validating; + /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -233,6 +235,8 @@ namespace osu.Game.Rulesets.Objects if (pathCache.IsValid) return; + Validating?.Invoke(); + calculatePath(); calculateLength(); From da4d83f8ca2c7ccf5ff651c80df656393b817b7f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 20:54:44 +0100 Subject: [PATCH 11/27] remove the need for caching points in segment --- .../Components/PathControlPointPiece.cs | 2 - .../Components/PathControlPointVisualiser.cs | 57 +++++++++---------- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 9b40b39a9d..8ddc38c38e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action DragInProgress; public Action DragEnded; - public List PointsInSegment; - public readonly BindableBool IsSelected = new BindableBool(); public readonly PathControlPoint ControlPoint; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index a02a07f2b6..d057565c2b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -9,7 +9,6 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using Humanizer; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -55,9 +54,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components [Resolved(CanBeNull = true)] private IDistanceSnapProvider distanceSnapProvider { get; set; } - [UsedImplicitly] - private readonly IBindable hitObjectVersion; - public PathControlPointVisualiser(T hitObject, bool allowSelection) { this.hitObject = hitObject; @@ -70,8 +66,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Connections = new Container> { RelativeSizeAxes = Axes.Both }, Pieces = new Container> { RelativeSizeAxes = Axes.Both } }; - - hitObjectVersion = hitObject.Path.Version.GetBoundCopy(); } protected override void LoadComplete() @@ -85,20 +79,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // schedule ensure that updates are only applied after all operations from a single frame are applied. // this avoids inadvertently changing the hit object path type for batch operations. - hitObject.Path.Validating += cachePointsAndEnsureValidPathTypes; + hitObject.Path.Validating += ensureValidPathTypes; } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - hitObject.Path.Validating -= cachePointsAndEnsureValidPathTypes; + hitObject.Path.Validating -= ensureValidPathTypes; } /// - /// Caches the PointsInSegment of Pieces and handles correction of invalid path types. + /// Handles correction of invalid path types. /// - private void cachePointsAndEnsureValidPathTypes() + private void ensureValidPathTypes() { List pointsInCurrentSegment = new List(); @@ -107,31 +101,33 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (controlPoint.Type != null) { pointsInCurrentSegment.Add(controlPoint); - pointsInCurrentSegment = new List(); + ensureValidPathType(pointsInCurrentSegment); + pointsInCurrentSegment.Clear(); } pointsInCurrentSegment.Add(controlPoint); - - // Pieces might not be ordered so we need to find the piece corresponding to the current control point. - Pieces.Single(o => o.ControlPoint == controlPoint).PointsInSegment = pointsInCurrentSegment; } - foreach (var piece in Pieces) - { - if (piece.ControlPoint.Type != PathType.PERFECT_CURVE) - continue; + ensureValidPathType(pointsInCurrentSegment); + } - if (piece.PointsInSegment.Count > 3) - piece.ControlPoint.Type = PathType.BEZIER; + private void ensureValidPathType(IReadOnlyList segment) + { + var first = segment[0]; - if (piece.PointsInSegment.Count != 3) - continue; + if (first.Type != PathType.PERFECT_CURVE) + return; - ReadOnlySpan points = piece.PointsInSegment.Select(p => p.Position).ToArray(); - RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); - if (boundingBox.Width >= 640 || boundingBox.Height >= 480) - piece.ControlPoint.Type = PathType.BEZIER; - } + if (segment.Count > 3) + first.Type = PathType.BEZIER; + + if (segment.Count != 3) + return; + + ReadOnlySpan points = segment.Select(p => p.Position).ToArray(); + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + first.Type = PathType.BEZIER; } /// @@ -298,7 +294,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// The path type we want to assign to the given control point piece. private void updatePathType(PathControlPointPiece piece, PathType? type) { - int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); + var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint); + int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint); if (type?.Type == SplineType.PerfectCurve) { @@ -307,8 +304,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // and one segment of the previous type. int thirdPointIndex = indexInSegment + 2; - if (piece.PointsInSegment.Count > thirdPointIndex + 1) - piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; + if (pointsInSegment.Count > thirdPointIndex + 1) + pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type; } hitObject.Path.ExpectedDistance.Value = null; From 1e3c332658a320fb2dbf49e4a97e2927cafc4dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jan 2024 21:39:32 +0100 Subject: [PATCH 12/27] Fix broken installer Closes https://github.com/ppy/osu/issues/26510. Time for a rant. Technically, this "broke" with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0, but it is actually an end result of upstream behaviours that I am failing to find a better description for than "utterly broken". Squirrel (the installer we use) has unit tests. Which is great, power to them. However, the method in which that testing is implemented leads to epic levels of WTF breakage. To determine whether Squirrel is being tested right now, it is checking all currently loaded assemblies, and determining that if any loaded assembly contains the magic string of "NUNIT" - among others - it must be being tested right now: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 If one assumes that there is no conceivable way that an NUnit assembly *may* be loaded *without* it being a test context, this *may* seem sane. Foreshadowing. (Now, to avoid being hypocritical, we also do this, *but* we do this by checking if the *entry* assembly is an NUnit: https://github.com/ppy/osu-framework/blob/92db55a52742047b42c0b4b25327ce28bd991b44/osu.Framework/Development/DebugUtils.cs#L16-L34 which seems *much* saner, no?) Now, why did this break with 9e8d07d3144bd4b072d28bd9bd0e255fee410de0 *specifically*, you might wonder? Well the reason is this line: https://github.com/ppy/osu/blob/3d3f58c2523f519504d9156a684538e2aa0c094c/osu.Desktop/NVAPI.cs#L183 Yes you are reading this correctly, it's not NVAPI anything itself that breaks this, it is *a log statement*. To be precise, what the log statement *does* to provoke this, is calling into framework. That causes the framework assembly to load, *which* transitively loads the `nunit.framework` assembly. (If you ever find yourself wanting to find out this sort of cursed knowledge - I hope you never need to - you can run something along the lines of dotnet-trace collect --providers Microsoft-Windows-DotNETRuntime:4 -- .\osu!.exe then open the resulting trace in PerfView, and then search the `Microsoft-Windows-DotNETRuntime/AssemblyLoader/Start` log for the cursed assembly. In this case, the relevant entry said something along the lines of HasStack="True" ThreadID="23,924" ProcessorNumber="0" ClrInstanceID="6" AssemblyName="nunit.framework, Version=3.13.3.0, Culture=neutral, PublicKeyToken=2638cd05610744eb" AssemblyPath="" RequestingAssembly="osu.Framework, Version=2024.113.0.0, Culture=neutral, PublicKeyToken=null" AssemblyLoadContext="Default" RequestingAssemblyLoadContext="Default" ActivityID="/#21032/1/26/" Either that or just comment the log line for kicks. But the above is *much* faster.) Now, what *happens* if Squirrel "detects" that it is being "tested"? Well, it will refuse to close after executing the "hooks" defined via `SquirrelAwareApp`: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SquirrelAwareApp.cs#L85-L88 and it will also refuse to create version shortcuts: https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/UpdateManager.Shortcuts.cs#L63-L65 Sounds familiar, don't it? There are days on which I tire of computers. Today is one of them. --- osu.Desktop/Program.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index a652e31f62..6b95a82703 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -30,12 +30,19 @@ namespace osu.Desktop [STAThread] public static void Main(string[] args) { - // NVIDIA profiles are based on the executable name of a process. - // Lazer and stable share the same executable name. - // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. - NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; - - // run Squirrel first, as the app may exit after these run + /* + * WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK! + * + * Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it. + * To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit, + * namely by checking loaded assemblies: + * https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32 + * + * If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded - + * the app will then do completely broken things like: + * - not creating system shortcuts (as the logic is if'd out if "running tests") + * - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests") + */ if (OperatingSystem.IsWindows()) { var windowsVersion = Environment.OSVersion.Version; @@ -59,6 +66,11 @@ namespace osu.Desktop setupSquirrel(); } + // NVIDIA profiles are based on the executable name of a process. + // Lazer and stable share the same executable name. + // Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup. + NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT; + // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From 39908f5425d99d04e4c09b833c170193f3715205 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 22:39:09 +0100 Subject: [PATCH 13/27] remove Validating event and instead call validation explicitly on edits --- .../Components/PathControlPointVisualiser.cs | 20 ++++++++----------- .../Sliders/SliderPlacementBlueprint.cs | 2 ++ .../Sliders/SliderSelectionBlueprint.cs | 4 ++++ osu.Game/Rulesets/Objects/SliderPath.cs | 4 ---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index d057565c2b..b2d1709531 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -76,23 +76,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components controlPoints.CollectionChanged += onControlPointsChanged; controlPoints.BindTo(hitObject.Path.ControlPoints); - - // schedule ensure that updates are only applied after all operations from a single frame are applied. - // this avoids inadvertently changing the hit object path type for batch operations. - hitObject.Path.Validating += ensureValidPathTypes; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - hitObject.Path.Validating -= ensureValidPathTypes; } /// /// Handles correction of invalid path types. /// - private void ensureValidPathTypes() + public void EnsureValidPathTypes() { List pointsInCurrentSegment = new List(); @@ -113,6 +102,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void ensureValidPathType(IReadOnlyList segment) { + if (segment.Count == 0) + return; + var first = segment[0]; if (first.Type != PathType.PERFECT_CURVE) @@ -394,6 +386,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // Maintain the path types in case they got defaulted to bezier at some point during the drag. for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++) hitObject.Path.ControlPoints[i].Type = dragPathTypes[i]; + + EnsureValidPathTypes(); } public void DragEnded() => changeHandler?.EndChange(); @@ -467,6 +461,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { foreach (var p in Pieces.Where(p => p.IsSelected.Value)) updatePathType(p, type); + + EnsureValidPathTypes(); }); if (countOfState == totalCount) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 28e972bacd..75ed818693 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -316,6 +316,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { + controlPointVisualiser.EnsureValidPathTypes(); + if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b3efe1c495..3575e15d1d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Move the control points from the insertion index onwards to make room for the insertion controlPoints.Insert(insertionIndex, pathControlPoint); + ControlPointVisualiser?.EnsureValidPathTypes(); + HitObject.SnapTo(distanceSnapProvider); return pathControlPoint; @@ -275,6 +277,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders controlPoints.Remove(c); } + ControlPointVisualiser?.EnsureValidPathTypes(); + // Snap the slider to the current beat divisor before checking length validity. HitObject.SnapTo(distanceSnapProvider); diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index bafc62ceda..dc71608132 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Objects private readonly Bindable version = new Bindable(); - public event Action? Validating; - /// /// The user-set distance of the path. If non-null, will match this value, /// and the path will be shortened/lengthened to match this length. @@ -235,8 +233,6 @@ namespace osu.Game.Rulesets.Objects if (pathCache.IsValid) return; - Validating?.Invoke(); - calculatePath(); calculateLength(); From 83e108071a123650991fded528f64ad80421d155 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 22:51:33 +0100 Subject: [PATCH 14/27] fix wrong path type being displayed --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 75ed818693..0fa84c91fc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -267,6 +267,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders segmentStart.Type = PathType.BEZIER; break; } + + controlPointVisualiser.EnsureValidPathTypes(); } private void updateCursor() @@ -316,8 +318,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - controlPointVisualiser.EnsureValidPathTypes(); - if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else From 243b7b6fdad6aa28df1df8a06ecf574ebeff1238 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 13 Jan 2024 23:17:38 +0100 Subject: [PATCH 15/27] fix code quality --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 8ddc38c38e..e741d67e3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; From 68496f7a0e865b3d1d74415d482e78a4747bd454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jan 2024 15:13:20 +0900 Subject: [PATCH 16/27] Fix scores not showing up on leaderboards during gameplay --- osu.Game/Screens/Select/PlaySongSelect.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 4951504ff5..7b7b8857f3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -146,14 +146,6 @@ namespace osu.Game.Screens.Select } } - public override void OnSuspending(ScreenTransitionEvent e) - { - // Scores will be refreshed on arriving at this screen. - // Clear them to avoid animation overload on returning to song select. - playBeatmapDetailArea.Leaderboard.ClearScores(); - base.OnSuspending(e); - } - public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); From b7d74fda88b98cd8b78df7ae5e907cc8b8fb9c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:10:39 +0100 Subject: [PATCH 17/27] Revert "Keep editor in frame stable mode when possible" --- .../TestSceneEditorAutoplayFastStreams.cs | 51 ------------------- .../Editor/TestSceneOsuComposerSelection.cs | 2 - .../Edit/DrawableEditorRulesetWrapper.cs | 21 +------- 3 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs deleted file mode 100644 index cf5cd809ef..0000000000 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneEditorAutoplayFastStreams.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Linq; -using NUnit.Framework; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Beatmaps; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests.Editor -{ - /// - /// This test covers autoplay working correctly in the editor on fast streams. - /// Might seem like a weird test, but frame stability being toggled can cause autoplay to operation incorrectly. - /// This is clearly a bug with the autoplay algorithm, but is worked around at an editor level for now. - /// - public partial class TestSceneEditorAutoplayFastStreams : EditorTestScene - { - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - var testBeatmap = new TestBeatmap(ruleset, false); - testBeatmap.HitObjects.AddRange(new[] - { - new HitCircle { StartTime = 500 }, - new HitCircle { StartTime = 530 }, - new HitCircle { StartTime = 560 }, - new HitCircle { StartTime = 590 }, - new HitCircle { StartTime = 620 }, - }); - - return testBeatmap; - } - - protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); - - [Test] - public void TestAllHit() - { - AddStep("start playback", () => EditorClock.Start()); - AddUntilStep("wait for all hit", () => - { - DrawableHitCircle[] hitCircles = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).ToArray(); - - return hitCircles.Length == 5 && hitCircles.All(h => h.IsHit); - }); - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 366f17daee..623cefff6b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -46,12 +46,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor moveMouseToObject(() => slider); AddStep("seek after end", () => EditorClock.Seek(750)); - AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddAssert("slider not selected", () => EditorBeatmap.SelectedHitObjects.Count == 0); AddStep("seek to visible", () => EditorClock.Seek(650)); - AddUntilStep("wait for seek", () => !EditorClock.IsSeeking); AddStep("left click", () => InputManager.Click(MouseButton.Left)); AddUntilStep("slider selected", () => EditorBeatmap.SelectedHitObjects.Single() == slider); } diff --git a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs index ebf06bcc4e..174b278d89 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditorRulesetWrapper.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.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -27,9 +26,6 @@ namespace osu.Game.Rulesets.Edit [Resolved] private EditorBeatmap beatmap { get; set; } = null!; - [Resolved] - private EditorClock editorClock { get; set; } = null!; - public DrawableEditorRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -42,6 +38,7 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load() { + drawableRuleset.FrameStablePlayback = false; Playfield.DisplayJudgements.Value = false; } @@ -68,22 +65,6 @@ namespace osu.Game.Rulesets.Edit Scheduler.AddOnce(regenerateAutoplay); } - protected override void Update() - { - base.Update(); - - // Whenever possible, we want to stay in frame stability playback. - // Without doing so, we run into bugs with some gameplay elements not behaving as expected. - // - // Note that this is not using EditorClock.IsSeeking as that would exit frame stability - // on all seeks. The intention here is to retain frame stability for small seeks. - // - // I still think no gameplay elements should require frame stability in the first place, but maybe that ship has sailed already.. - bool shouldBypassFrameStability = Math.Abs(drawableRuleset.FrameStableClock.CurrentTime - editorClock.CurrentTime) > 1000; - - drawableRuleset.FrameStablePlayback = !shouldBypassFrameStability; - } - private void regenerateAutoplay() { var autoplayMod = drawableRuleset.Mods.OfType().Single(); From eecd868d66a2fe928b76756996d25e588b383309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:24:50 +0100 Subject: [PATCH 18/27] Use darker blue for `SliderTailHit` result --- osu.Game/Graphics/OsuColour.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 0d11d2d4ef..1b5877b966 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -95,6 +95,7 @@ namespace osu.Game.Graphics case HitResult.SmallTickHit: case HitResult.LargeTickHit: + case HitResult.SliderTailHit: case HitResult.Great: return Blue; From 1cd7656f331172a281b12552c62b1f52ca2bad1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 09:33:04 +0100 Subject: [PATCH 19/27] Reorder hit results so that `SliderTailHit` is next to `SmallTickHit` This addresses https://github.com/ppy/osu/discussions/26507. --- osu.Game/Rulesets/Scoring/HitResult.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index f307344347..7e58df3cfa 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [EnumMember(Value = "none")] - [Order(14)] + [Order(15)] None, /// @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [EnumMember(Value = "small_tick_miss")] - [Order(11)] + [Order(12)] SmallTickMiss, /// @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Scoring /// [EnumMember(Value = "large_tick_miss")] [Description("-")] - [Order(10)] + [Order(11)] LargeTickMiss, /// @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [EnumMember(Value = "small_bonus")] - [Order(9)] + [Order(10)] SmallBonus, /// @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description("L Bonus")] [EnumMember(Value = "large_bonus")] - [Order(8)] + [Order(9)] LargeBonus, /// @@ -119,14 +119,14 @@ namespace osu.Game.Rulesets.Scoring /// [EnumMember(Value = "ignore_miss")] [Description("-")] - [Order(13)] + [Order(14)] IgnoreMiss, /// /// Indicates a hit that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_hit")] - [Order(12)] + [Order(13)] IgnoreHit, /// @@ -136,14 +136,14 @@ namespace osu.Game.Rulesets.Scoring /// May be paired with . /// [EnumMember(Value = "combo_break")] - [Order(15)] + [Order(16)] ComboBreak, /// /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. /// [EnumMember(Value = "slider_tail_hit")] - [Order(16)] + [Order(8)] SliderTailHit, /// From f85e6add8e7ba95463622904d26202e140ed4434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 12:55:07 +0100 Subject: [PATCH 20/27] Add failing test case --- .../Mods/TestSceneOsuModFlashlight.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index a353914cd5..defbb97c8a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -1,8 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { @@ -21,5 +31,51 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true }); + + [Test] + public void TestSliderDimsOnlyAfterStartTime() + { + bool sliderDimmedBeforeStartTime = false; + + CreateModTest(new ModTestData + { + Mod = new OsuModFlashlight(), + PassCondition = () => + { + sliderDimmedBeforeStartTime |= + Player.GameplayClockContainer.CurrentTime < 1000 && Player.ChildrenOfType.Flashlight>().Single().FlashlightDim > 0; + return Player.GameplayState.HasPassed && !sliderDimmedBeforeStartTime; + }, + Beatmap = new OsuBeatmap + { + HitObjects = new List + { + new HitCircle { StartTime = 0, }, + new Slider + { + StartTime = 1000, + Path = new SliderPath(new[] + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + }) + } + }, + BeatmapInfo = + { + StackLeniency = 0, + } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(990, new Vector2()), + new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(2000, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(2001, new Vector2(100)), + }, + Autoplay = false, + }); + } } } From 0b2b1fc58888b430c16e81119fe2a277567775de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 13:05:02 +0100 Subject: [PATCH 21/27] Fix flashlight dim being applied before slider start time Closes https://github.com/ppy/osu/issues/26515. Compare https://github.com/ppy/osu/pull/26053. --- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 252d7e2762..46c9bdd17f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableHitObject(DrawableHitObject drawable) { if (drawable is DrawableSlider s) - s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + s.Tracking.ValueChanged += _ => flashlight.OnSliderTrackingChange(s); } private partial class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSmoothness = 1.4f; } - public void OnSliderTrackingChange(ValueChangedEvent e) + public void OnSliderTrackingChange(DrawableSlider e) { // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield. - FlashlightDim = e.NewValue ? 0.8f : 0.0f; + FlashlightDim = Time.Current >= e.HitObject.StartTime && e.Tracking.Value ? 0.8f : 0.0f; } protected override bool OnMouseMove(MouseMoveEvent e) From baf3867e17c5d10e63433dcdf429095bce9deaa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jan 2024 13:48:47 +0100 Subject: [PATCH 22/27] Fix date failing to display on leaderboard for some scores with weird datetimes Addresses https://github.com/ppy/osu/discussions/26517. The score reported has a datetime of 0001/1/1 05:00:00 AM. Bit of a dodge fix but maybe fine? --- osu.Game/Extensions/TimeDisplayExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index 98633958ee..1b224cfeb7 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -59,7 +59,8 @@ namespace osu.Game.Extensions /// A short relative string representing the input time. public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) { - if (time == default) + // covers all `DateTimeOffset` instances with the date portion of 0001-01-01. + if (time.Date == default) return "-"; var now = DateTime.Now; From fb4f8d0834ee2dee64eda3e5ba8054faa26130eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jan 2024 22:31:48 +0900 Subject: [PATCH 23/27] Update framework --- 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 e39143ab93..a7376aa5a7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9afeaf7338..98e8b136e5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 724b4c9507b886fa8af21c6384d01ae0752305d4 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 14 Jan 2024 21:09:49 +0100 Subject: [PATCH 24/27] Expand click target of toolbar buttons and clock --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 92 ++++++++++--------- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 77 +++++++++------- .../Overlays/Toolbar/ToolbarHomeButton.cs | 2 +- .../Overlays/Toolbar/ToolbarMusicButton.cs | 2 +- .../Toolbar/ToolbarRulesetTabButton.cs | 2 +- .../Overlays/Toolbar/ToolbarSettingsButton.cs | 2 +- .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 7 files changed, 97 insertions(+), 82 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 03a1cfc005..36ff703c82 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -63,6 +63,7 @@ namespace osu.Game.Overlays.Toolbar protected virtual Anchor TooltipAnchor => Anchor.TopLeft; + protected readonly Container ButtonContent; protected ConstrainedIconContainer IconContainer; protected SpriteText DrawableText; protected Box HoverBackground; @@ -80,59 +81,66 @@ namespace osu.Game.Overlays.Toolbar protected ToolbarButton() { - Width = Toolbar.HEIGHT; + AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; - Padding = new MarginPadding(3); - Children = new Drawable[] { - BackgroundContent = new Container + ButtonContent = new Container { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 6, - CornerExponent = 3f, - Children = new Drawable[] - { - HoverBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, - }, - } - }, - Flow = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, + Width = Toolbar.HEIGHT, RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + Padding = new MarginPadding(3), Children = new Drawable[] { - IconContainer = new ConstrainedIconContainer + BackgroundContent = new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Alpha = 0, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + HoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, - DrawableText = new OsuSpriteText + Flow = new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding { Left = Toolbar.HEIGHT / 2, Right = Toolbar.HEIGHT / 2 }, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + IconContainer = new ConstrainedIconContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20), + Alpha = 0, + }, + DrawableText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }, }, }, }, diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index 67688155ae..e7959986ae 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -42,52 +42,59 @@ namespace osu.Game.Overlays.Toolbar clockDisplayMode = config.GetBindable(OsuSetting.ToolbarClockDisplayMode); prefer24HourTime = config.GetBindable(OsuSetting.Prefer24HourTime); - Padding = new MarginPadding(3); - Children = new Drawable[] { new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 6, - CornerExponent = 3f, - Children = new Drawable[] - { - hoverBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, - }, - } - }, - new FillFlowContainer { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Padding = new MarginPadding(10), + Padding = new MarginPadding(3), Children = new Drawable[] { - analog = new AnalogClockDisplay + new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, - digital = new DigitalClockDisplay + new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Padding = new MarginPadding(10), + Children = new Drawable[] + { + analog = new AnalogClockDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + digital = new DigitalClockDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 70675c1b92..499ca804c9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -12,7 +12,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Width *= 1.4f; + ButtonContent.Width *= 1.4f; Hotkey = GlobalAction.Home; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 69597c6b46..5da0056787 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarMusicButton() { Hotkey = GlobalAction.ToggleNowPlaying; - AutoSizeAxes = Axes.X; + ButtonContent.AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 5500f1c879..98a0191e0a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar public RulesetButton() { - Padding = new MarginPadding(3) + ButtonContent.Padding = new MarginPadding(3) { Bottom = 5 }; diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 78df060252..899f58c9c0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Width *= 1.4f; + ButtonContent.Width *= 1.4f; Hotkey = GlobalAction.ToggleSettings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 19b98628e6..230974cd59 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarUserButton() { - AutoSizeAxes = Axes.X; + ButtonContent.AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader] From 47b385c552462c98705e4a8a8c578cf6e8a42419 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sun, 14 Jan 2024 21:13:00 +0100 Subject: [PATCH 25/27] Move toolbar button padding to a const --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 4 +++- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 36ff703c82..1da2e1b744 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -26,6 +26,8 @@ namespace osu.Game.Overlays.Toolbar { public abstract partial class ToolbarButton : OsuClickableContainer, IKeyBindingHandler { + public const float PADDING = 3; + protected GlobalAction? Hotkey { get; set; } public void SetIcon(Drawable icon) @@ -90,7 +92,7 @@ namespace osu.Game.Overlays.Toolbar { Width = Toolbar.HEIGHT, RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding(3), + Padding = new MarginPadding(PADDING), Children = new Drawable[] { BackgroundContent = new Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index e7959986ae..e1d658b811 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, - Padding = new MarginPadding(3), + Padding = new MarginPadding(ToolbarButton.PADDING), Children = new Drawable[] { new Container diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 98a0191e0a..c224f0f9c9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Toolbar public RulesetButton() { - ButtonContent.Padding = new MarginPadding(3) + ButtonContent.Padding = new MarginPadding(PADDING) { Bottom = 5 }; From cd20561843874df1eab24e179201eb1742e77520 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:12:10 +0900 Subject: [PATCH 26/27] Adjust text slightly --- osu.Game/Localisation/GraphicsSettingsStrings.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index fc29352ae2..1d14b0a596 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -152,14 +152,13 @@ namespace osu.Game.Localisation /// /// "In order to change the renderer, the game will close. Please open it again." /// - public static LocalisableString ChangeRendererConfirmation => - new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); + public static LocalisableString ChangeRendererConfirmation => new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again."); /// - /// "Minimise on focus loss" + /// "Minimise osu! when switching to another app" /// - public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise on focus loss"); + public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From a6c309b61a1edc109dfe0b3b2ff0dca57564628d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jan 2024 14:12:39 +0900 Subject: [PATCH 27/27] Add more keywords --- osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index adb572e245..71afec88d4 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = GraphicsSettingsStrings.MinimiseOnFocusLoss, Current = config.GetBindable(FrameworkSetting.MinimiseOnFocusLossInFullscreen), - Keywords = new[] { "alt-tab" }, + Keywords = new[] { "alt-tab", "minimize", "focus", "hide" }, }, safeAreaConsiderationsCheckbox = new SettingsCheckbox {