From 5b3eb2d6f443ea90322787f28033e77335648cbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Mar 2022 23:54:00 +0900 Subject: [PATCH 01/48] Add helper class to handle firing async multiplayer methods --- .../MultiplayerClientExtensions.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs new file mode 100644 index 0000000000..98c593236a --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs @@ -0,0 +1,43 @@ +// 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 System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using osu.Framework.Logging; + +namespace osu.Game.Online.Multiplayer +{ + public static class MultiplayerClientExtensions + { + public static void FireAndForget(this Task task, Action? onSuccess = null, Action? onError = null) => + task.ContinueWith(t => + { + if (t.IsFaulted) + { + Exception? exception = t.Exception; + + if (exception is AggregateException ae) + exception = ae.InnerException; + + Debug.Assert(exception != null); + + string message = exception is HubException + // HubExceptions arrive with additional message context added, but we want to display the human readable message: + // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once." + // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now. + ? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim() + : exception.Message; + + Logger.Log(message, level: LogLevel.Important); + onError?.Invoke(exception); + } + else + { + onSuccess?.Invoke(); + } + }); + } +} From e0c125a628c5848f01b4f8c9616c4cf75e6c2667 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Mar 2022 23:57:19 +0900 Subject: [PATCH 02/48] Replace existing usage with helper method --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 49 ++++++------------- 1 file changed, 14 insertions(+), 35 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index d49c122bd1..2e8f625eba 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -1,13 +1,9 @@ // 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.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; using osu.Framework.Allocation; -using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; @@ -76,40 +72,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem); - task.ContinueWith(t => + task.FireAndForget(onSuccess: () => Schedule(() => { - Schedule(() => - { - // If an error or server side trigger occurred this screen may have already exited by external means. - if (!this.IsCurrentScreen()) - return; + // If an error or server side trigger occurred this screen may have already exited by external means. + if (!this.IsCurrentScreen()) + return; - loadingLayer.Hide(); + loadingLayer.Hide(); + this.Exit(); + }), onError: _ => Schedule(() => + { + // If an error or server side trigger occurred this screen may have already exited by external means. + if (!this.IsCurrentScreen()) + return; - if (t.IsFaulted) - { - Exception exception = t.Exception; - - if (exception is AggregateException ae) - exception = ae.InnerException; - - Debug.Assert(exception != null); - - string message = exception is HubException - // HubExceptions arrive with additional message context added, but we want to display the human readable message: - // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once." - // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now. - ? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim() - : exception.Message; - - Logger.Log(message, level: LogLevel.Important); - Carousel.AllowSelection = true; - return; - } - - this.Exit(); - }); - }); + loadingLayer.Hide(); + Carousel.AllowSelection = true; + })); } else { From 0b6db315111cdc4756c7eda240836f2a633a6d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Mar 2022 23:58:30 +0900 Subject: [PATCH 03/48] Guard other multiplayer client calls with exception handling --- .../Multiplayer/Match/MatchStartControl.cs | 17 ++++++++--------- .../Match/Playlist/MultiplayerQueueList.cs | 2 +- .../OnlinePlay/Multiplayer/Multiplayer.cs | 2 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 6 +++--- .../Participants/ParticipantPanel.cs | 6 +++--- .../Multiplayer/Participants/TeamDisplay.cs | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 1201279929..d048676872 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -114,18 +114,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool isReady() => Client.LocalUser?.State == MultiplayerUserState.Ready || Client.LocalUser?.State == MultiplayerUserState.Spectating; - void toggleReady() => Client.ToggleReady().ContinueWith(_ => endOperation()); + void toggleReady() => Client.ToggleReady().FireAndForget( + onSuccess: endOperation, + onError: _ => endOperation()); - void startMatch() => Client.StartMatch().ContinueWith(t => + void startMatch() => Client.StartMatch().FireAndForget(onSuccess: () => { - // accessing Exception here silences any potential errors from the antecedent task - if (t.Exception != null) - { - // gameplay was not started due to an exception; unblock button. - endOperation(); - } - // gameplay is starting, the button will be unblocked on load requested. + }, onError: _ => + { + // gameplay was not started due to an exception; unblock button. + endOperation(); }); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 1653d416d8..d72ce5e960 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { base.LoadComplete(); - RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); + RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID).FireAndForget(); multiplayerClient.RoomUpdated += onRoomUpdated; onRoomUpdated(); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index 28c9bef3f0..2482d52492 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // If gameplay wasn't finished, then we have a simple path back to the idle state by aborting gameplay. if (!playerLoader.GameplayPassed) { - client.AbortGameplay(); + client.AbortGameplay().FireAndForget(); return; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index e53153e017..7ef0bb5923 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -281,7 +281,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; - client.ChangeUserMods(mods.NewValue); + client.ChangeUserMods(mods.NewValue).FireAndForget(); modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); modSettingChangeTracker.SettingChanged += onModSettingsChanged; @@ -296,7 +296,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; - client.ChangeUserMods(UserMods.Value); + client.ChangeUserMods(UserMods.Value).FireAndForget(); }, 500); } @@ -305,7 +305,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; - client.ChangeBeatmapAvailability(availability.NewValue); + client.ChangeBeatmapAvailability(availability.NewValue).FireAndForget(); if (availability.NewValue.State != DownloadState.LocallyAvailable) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 7ba0a63856..e091559046 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Origin = Anchor.Centre, Alpha = 0, Margin = new MarginPadding(4), - Action = () => Client.KickUser(User.UserID), + Action = () => Client.KickUser(User.UserID).FireAndForget(), }, }, } @@ -231,7 +231,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (!Client.IsHost) return; - Client.TransferHost(targetUser); + Client.TransferHost(targetUser).FireAndForget(); }), new OsuMenuItem("Kick", MenuItemType.Destructive, () => { @@ -239,7 +239,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants if (!Client.IsHost) return; - Client.KickUser(targetUser); + Client.KickUser(targetUser).FireAndForget(); }) }; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs index 73aca0acdc..aca2c6073a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Client.SendMatchRequest(new ChangeTeamRequest { TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0, - }); + }).FireAndForget(); } public int? DisplayedTeam { get; private set; } From 293ef4483609737f34bf5796d0f8fa09b84b177c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Mar 2022 22:43:17 +0100 Subject: [PATCH 04/48] Implement new mod select screen --- .../UserInterface/TestSceneModSelectScreen.cs | 28 ++ osu.Game/Overlays/Mods/ModColumn.cs | 11 +- osu.Game/Overlays/Mods/ModSelectScreen.cs | 315 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSettingsArea.cs | 4 +- 4 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs create mode 100644 osu.Game/Overlays/Mods/ModSelectScreen.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs new file mode 100644 index 0000000000..e6a5784536 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -0,0 +1,28 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModSelectScreen : OsuTestScene + { + [Test] + public void TestModSelectScreen() + { + ModSelectScreen modSelectScreen = null; + + AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + }); + + AddToggleStep("toggle state", visible => modSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 736a0205e2..9cf9e6a268 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -48,6 +48,8 @@ namespace osu.Game.Overlays.Mods } } + public Action? ModStateChanged { get; set; } + private readonly ModType modType; private readonly Key[]? toggleKeys; @@ -251,7 +253,14 @@ namespace osu.Game.Overlays.Mods panelFlow.ChildrenEnumerable = loaded; foreach (var panel in panelFlow) - panel.Active.BindValueChanged(_ => updateToggleState()); + { + panel.Active.BindValueChanged(_ => + { + updateToggleState(); + ModStateChanged?.Invoke(panel.Mod, panel.Active.Value); + }); + } + updateToggleState(); updateFilter(); diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs new file mode 100644 index 0000000000..bd27407965 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -0,0 +1,315 @@ +// 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.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Layout; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods +{ + public class ModSelectScreen : OsuFocusedOverlayContainer + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Cached] + private Bindable> selectedMods = new Bindable>(Array.Empty()); + + private readonly BindableBool customisationVisible = new BindableBool(); + + private DifficultyMultiplierDisplay multiplierDisplay; + private ModSettingsArea modSettingsArea; + private Container columnContainer; + private FillFlowContainer columnFlow; + private GridContainer grid; + private Container mainContent; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + mainContent = new Container + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + grid = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 75), + }, + Content = new[] + { + new Drawable[] + { + new PopupScreenTitle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Title = "Mod Select", + Description = "Mods provide different ways to enjoy gameplay. Some have an effect on the score you can achieve during ranked play. Others are just for fun.", + Close = Hide + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 100, Vertical = 10 }, + Child = multiplierDisplay = new DifficultyMultiplierDisplay + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight + } + } + }, + new Drawable[] + { + columnContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + X = 1, + Padding = new MarginPadding { Horizontal = 70 }, + Children = new Drawable[] + { + new OsuScrollContainer(Direction.Horizontal) + { + RelativeSizeAxes = Axes.Both, + Masking = false, + ScrollbarOverlapsContent = false, + Child = columnFlow = new ModColumnContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Spacing = new Vector2(10, 0), + Children = new[] + { + new ModColumn(ModType.DifficultyReduction, false, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), + new ModColumn(ModType.DifficultyIncrease, false, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }), + new ModColumn(ModType.Automation, false, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }), + new ModColumn(ModType.Conversion, false), + new ModColumn(ModType.Fun, false) + } + } + } + } + } + }, + new[] { Empty() } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 50, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = colourProvider.Background5 + }, + new ShearedToggleButton(200) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Vertical = 14, Left = 70 }, + Text = "Mod Customisation", + Active = { BindTarget = customisationVisible } + } + } + }, + new ClickToReturnContainer + { + RelativeSizeAxes = Axes.Both, + HandleMouse = { BindTarget = customisationVisible }, + OnClicked = () => customisationVisible.Value = false + } + } + }, + modSettingsArea = new ModSettingsArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre + } + }; + + foreach (var column in columnFlow) + { + column.ModStateChanged = (mod, active) => selectedMods.Value = active + ? selectedMods.Value.Append(mod).ToArray() + : selectedMods.Value.Except(new[] { mod }).ToArray(); + } + + columnFlow.Shear = new Vector2(ModPanel.SHEAR_X, 0); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ((IBindable>)modSettingsArea.SelectedMods).BindTo(selectedMods); + selectedMods.BindValueChanged(val => + { + updateMultiplier(); + updateCustomisation(val); + }, true); + customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); + + FinishTransforms(true); + } + + private void updateMultiplier() + { + double multiplier = 1.0; + + foreach (var mod in selectedMods.Value) + multiplier *= mod.ScoreMultiplier; + + multiplierDisplay.Current.Value = multiplier; + } + + private void updateCustomisation(ValueChangedEvent> valueChangedEvent) + { + bool anyCustomisableMod = false; + bool anyModWithRequiredCustomisationAdded = false; + + foreach (var mod in selectedMods.Value) + { + anyCustomisableMod |= mod.GetSettingsSourceProperties().Any(); + anyModWithRequiredCustomisationAdded |= !valueChangedEvent.OldValue.Contains(mod) && mod.RequiresConfiguration; + } + + if (anyCustomisableMod) + { + customisationVisible.Disabled = false; + + if (anyModWithRequiredCustomisationAdded && !customisationVisible.Value) + customisationVisible.Value = true; + } + else + { + if (customisationVisible.Value) + customisationVisible.Value = false; + + customisationVisible.Disabled = true; + } + } + + private void updateCustomisationVisualState() + { + grid.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, 800, Easing.OutQuint); + + float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0; + + modSettingsArea.ResizeHeightTo(modAreaHeight, 800, Easing.InOutCubic); + mainContent.TransformTo(nameof(Margin), new MarginPadding { Bottom = modAreaHeight }, 800, Easing.InOutCubic); + } + + protected override void PopIn() + { + base.PopIn(); + this.MoveToY(0, 800, Easing.OutQuint); + columnContainer.MoveToX(0, 800, Easing.OutQuint); + } + + protected override void PopOut() + { + base.PopOut(); + this.MoveToY(1.2f, 800, Easing.OutQuint); + columnContainer.MoveToX(1, 800, Easing.OutQuint); + } + + private class ModColumnContainer : FillFlowContainer + { + private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize); + + public ModColumnContainer() + { + AddLayout(drawSizeLayout); + } + + public override void Add(ModColumn column) + { + base.Add(column); + + Debug.Assert(column != null); + column.Shear = Vector2.Zero; + } + + protected override void Update() + { + base.Update(); + + if (!drawSizeLayout.IsValid) + { + Padding = new MarginPadding + { + Left = DrawHeight * ModPanel.SHEAR_X, + Bottom = 10 + }; + + drawSizeLayout.Validate(); + } + } + } + + private class ClickToReturnContainer : Container + { + public BindableBool HandleMouse { get; } = new BindableBool(); + + public Action OnClicked { get; set; } + + protected override bool Handle(UIEvent e) + { + if (!HandleMouse.Value) + return base.Handle(e); + + switch (e) + { + case ClickEvent _: + OnClicked?.Invoke(); + return true; + + case MouseEvent _: + return true; + } + + return base.Handle(e); + } + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs index e0a30f60c2..9b0382c6a4 100644 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -23,6 +23,8 @@ namespace osu.Game.Overlays.Mods { public Bindable> SelectedMods { get; } = new Bindable>(); + public const float HEIGHT = 250; + private readonly Box background; private readonly FillFlowContainer modSettingsFlow; @@ -32,7 +34,7 @@ namespace osu.Game.Overlays.Mods public ModSettingsArea() { RelativeSizeAxes = Axes.X; - Height = 250; + Height = HEIGHT; Anchor = Anchor.BottomRight; Origin = Anchor.BottomRight; From e46c2df409afe3bcf50e3a8bb40bb8f1bf90d0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Mar 2022 23:21:17 +0100 Subject: [PATCH 05/48] Add testing for customisation panel show/hide logic --- .../UserInterface/TestSceneModSelectScreen.cs | 62 ++++++++++++++++--- osu.Game/Overlays/Mods/ModSelectScreen.cs | 16 ++--- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index e6a5784536..25b2e92c0c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -1,28 +1,76 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneModSelectScreen : OsuTestScene + public class TestSceneModSelectScreen : OsuManualInputManagerTestScene { - [Test] - public void TestModSelectScreen() - { - ModSelectScreen modSelectScreen = null; + private ModSelectScreen modSelectScreen; + [SetUpSteps] + public void SetUpSteps() + { AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen { RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods } + }); + } + + [Test] + public void TestStateChange() + { + AddToggleStep("toggle state", visible => modSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden); + } + + [Test] + public void TestCustomisationToggleState() + { + assertCustomisationToggleState(disabled: true, active: false); + + AddStep("select customisable mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); + assertCustomisationToggleState(disabled: false, active: false); + + AddStep("select mod requiring configuration", () => SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); + assertCustomisationToggleState(disabled: false, active: true); + + AddStep("dismiss mod customisation", () => + { + InputManager.MoveMouseTo(modSelectScreen.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); }); - AddToggleStep("toggle state", visible => modSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden); + AddStep("append another mod not requiring config", () => SelectedMods.Value = SelectedMods.Value.Append(new OsuModFlashlight()).ToArray()); + assertCustomisationToggleState(disabled: false, active: false); + + AddStep("select mod without configuration", () => SelectedMods.Value = new[] { new OsuModAutoplay() }); + assertCustomisationToggleState(disabled: true, active: false); + + AddStep("select mod requiring configuration", () => SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); + assertCustomisationToggleState(disabled: false, active: true); + + AddStep("select mod without configuration", () => SelectedMods.Value = new[] { new OsuModAutoplay() }); + assertCustomisationToggleState(disabled: true, active: false); // config was dismissed without explicit user action. + } + + private void assertCustomisationToggleState(bool disabled, bool active) + { + ShearedToggleButton getToggle() => modSelectScreen.ChildrenOfType().Single(); + + AddAssert($"customisation toggle is {(disabled ? "" : "not ")}disabled", () => getToggle().Active.Disabled == disabled); + AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => getToggle().Active.Value == active); } } } diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index bd27407965..0a9b1ddbcb 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Mods private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); [Cached] - private Bindable> selectedMods = new Bindable>(Array.Empty()); + public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); private readonly BindableBool customisationVisible = new BindableBool(); @@ -169,9 +169,9 @@ namespace osu.Game.Overlays.Mods foreach (var column in columnFlow) { - column.ModStateChanged = (mod, active) => selectedMods.Value = active - ? selectedMods.Value.Append(mod).ToArray() - : selectedMods.Value.Except(new[] { mod }).ToArray(); + column.ModStateChanged = (mod, active) => SelectedMods.Value = active + ? SelectedMods.Value.Append(mod).ToArray() + : SelectedMods.Value.Except(new[] { mod }).ToArray(); } columnFlow.Shear = new Vector2(ModPanel.SHEAR_X, 0); @@ -181,8 +181,8 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - ((IBindable>)modSettingsArea.SelectedMods).BindTo(selectedMods); - selectedMods.BindValueChanged(val => + ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); + SelectedMods.BindValueChanged(val => { updateMultiplier(); updateCustomisation(val); @@ -196,7 +196,7 @@ namespace osu.Game.Overlays.Mods { double multiplier = 1.0; - foreach (var mod in selectedMods.Value) + foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; multiplierDisplay.Current.Value = multiplier; @@ -207,7 +207,7 @@ namespace osu.Game.Overlays.Mods bool anyCustomisableMod = false; bool anyModWithRequiredCustomisationAdded = false; - foreach (var mod in selectedMods.Value) + foreach (var mod in SelectedMods.Value) { anyCustomisableMod |= mod.GetSettingsSourceProperties().Any(); anyModWithRequiredCustomisationAdded |= !valueChangedEvent.OldValue.Contains(mod) && mod.RequiresConfiguration; From 2921a1360917b915852e678bed4b453c9e777235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Apr 2022 17:43:13 +0200 Subject: [PATCH 06/48] Add testing for mod bindable state propagation --- .../UserInterface/TestSceneModSelectScreen.cs | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 25b2e92c0c..a5f5048961 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -6,8 +6,10 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK.Input; @@ -21,23 +23,55 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUpSteps] public void SetUpSteps() { - AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - SelectedMods = { BindTarget = SelectedMods } - }); + AddStep("reset mods", () => SelectedMods.SetDefault()); } + private void createScreen() => AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods } + }); + [Test] public void TestStateChange() { + createScreen(); AddToggleStep("toggle state", visible => modSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden); } + [Test] + public void TestPreexistingSelection() + { + AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModAlternate(), new OsuModDaycore() }); + createScreen(); + AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("mod multiplier correct", () => + { + double multiplier = SelectedMods.Value.Aggregate(1d, (multiplier, mod) => multiplier * mod.ScoreMultiplier); + return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); + }); + assertCustomisationToggleState(disabled: false, active: false); + } + + [Test] + public void TestExternalSelection() + { + createScreen(); + AddStep("set mods", () => SelectedMods.Value = new Mod[] { new OsuModAlternate(), new OsuModDaycore() }); + AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("mod multiplier correct", () => + { + double multiplier = SelectedMods.Value.Aggregate(1d, (multiplier, mod) => multiplier * mod.ScoreMultiplier); + return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); + }); + assertCustomisationToggleState(disabled: false, active: false); + } + [Test] public void TestCustomisationToggleState() { + createScreen(); assertCustomisationToggleState(disabled: true, active: false); AddStep("select customisable mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); From cd776d21a6c5268277644bebd1fe3336cb75dbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Mar 2022 00:16:10 +0200 Subject: [PATCH 07/48] Fix propagation of selected mods to columns --- osu.Game/Overlays/Mods/ModColumn.cs | 43 +++++++++++++++-------- osu.Game/Overlays/Mods/ModSelectScreen.cs | 42 ++++++++++++++++++---- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 9cf9e6a268..46a562ac98 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Mods { public class ModColumn : CompositeDrawable { + public readonly ModType ModType; + private Func? filter; /// @@ -48,9 +50,8 @@ namespace osu.Game.Overlays.Mods } } - public Action? ModStateChanged { get; set; } + public Bindable> SelectedMods = new Bindable>(Array.Empty()); - private readonly ModType modType; private readonly Key[]? toggleKeys; private readonly Bindable>> availableMods = new Bindable>>(); @@ -71,7 +72,7 @@ namespace osu.Game.Overlays.Mods public ModColumn(ModType modType, bool allowBulkSelection, Key[]? toggleKeys = null) { - this.modType = modType; + ModType = modType; this.toggleKeys = toggleKeys; Width = 320; @@ -195,7 +196,7 @@ namespace osu.Game.Overlays.Mods private void createHeaderText() { - IEnumerable headerTextWords = modType.Humanize(LetterCasing.Title).Split(' '); + IEnumerable headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' '); if (headerTextWords.Count() > 1) { @@ -211,7 +212,7 @@ namespace osu.Game.Overlays.Mods { availableMods.BindTo(game.AvailableMods); - headerBackground.Colour = accentColour = colours.ForModType(modType); + headerBackground.Colour = accentColour = colours.ForModType(ModType); if (toggleAllCheckbox != null) { @@ -227,6 +228,12 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods)); + SelectedMods.BindValueChanged(_ => + { + // if a load is in progress, don't try to update the selection - the load flow will do so. + if (latestLoadTask == null) + updateActiveState(); + }); updateMods(); } @@ -234,7 +241,7 @@ namespace osu.Game.Overlays.Mods private void updateMods() { - var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(modType) ?? Array.Empty()).ToList(); + var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty()).ToList(); if (newMods.SequenceEqual(panelFlow.Children.Select(p => p.Mod))) return; @@ -252,18 +259,20 @@ namespace osu.Game.Overlays.Mods { panelFlow.ChildrenEnumerable = loaded; + updateActiveState(); + updateToggleAllState(); + updateFilter(); + foreach (var panel in panelFlow) { panel.Active.BindValueChanged(_ => { - updateToggleState(); - ModStateChanged?.Invoke(panel.Mod, panel.Active.Value); + updateToggleAllState(); + SelectedMods.Value = panel.Active.Value + ? SelectedMods.Value.Append(panel.Mod).ToArray() + : SelectedMods.Value.Except(new[] { panel.Mod }).ToArray(); }); } - - updateToggleState(); - - updateFilter(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => { @@ -272,6 +281,12 @@ namespace osu.Game.Overlays.Mods }); } + private void updateActiveState() + { + foreach (var panel in panelFlow) + panel.Active.Value = SelectedMods.Value.Contains(panel.Mod, EqualityComparer.Default); + } + #region Bulk select / deselect private const double initial_multiple_selection_delay = 120; @@ -306,7 +321,7 @@ namespace osu.Game.Overlays.Mods } } - private void updateToggleState() + private void updateToggleAllState() { if (toggleAllCheckbox != null && !SelectionAnimationRunning) { @@ -408,7 +423,7 @@ namespace osu.Game.Overlays.Mods foreach (var modPanel in panelFlow) modPanel.ApplyFilter(Filter); - updateToggleState(); + updateToggleAllState(); } #endregion diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 0a9b1ddbcb..659c88e070 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -167,13 +167,6 @@ namespace osu.Game.Overlays.Mods } }; - foreach (var column in columnFlow) - { - column.ModStateChanged = (mod, active) => SelectedMods.Value = active - ? SelectedMods.Value.Append(mod).ToArray() - : SelectedMods.Value.Except(new[] { mod }).ToArray(); - } - columnFlow.Shear = new Vector2(ModPanel.SHEAR_X, 0); } @@ -182,11 +175,19 @@ namespace osu.Game.Overlays.Mods base.LoadComplete(); ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); + SelectedMods.BindValueChanged(val => { updateMultiplier(); updateCustomisation(val); + updateSelectionFromBindable(); }, true); + + foreach (var column in columnFlow) + { + column.SelectedMods.BindValueChanged(_ => updateBindableFromSelection()); + } + customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); FinishTransforms(true); @@ -239,6 +240,33 @@ namespace osu.Game.Overlays.Mods mainContent.TransformTo(nameof(Margin), new MarginPadding { Bottom = modAreaHeight }, 800, Easing.InOutCubic); } + private bool selectionBindableSyncInProgress; + + private void updateSelectionFromBindable() + { + if (selectionBindableSyncInProgress) + return; + + selectionBindableSyncInProgress = true; + + foreach (var column in columnFlow) + column.SelectedMods.Value = SelectedMods.Value.Where(mod => mod.Type == column.ModType).ToArray(); + + selectionBindableSyncInProgress = false; + } + + private void updateBindableFromSelection() + { + if (selectionBindableSyncInProgress) + return; + + selectionBindableSyncInProgress = true; + + SelectedMods.Value = columnFlow.SelectMany(column => column.SelectedMods.Value).ToArray(); + + selectionBindableSyncInProgress = false; + } + protected override void PopIn() { base.PopIn(); From a172b194f0a97d546e7af99ee34f93512cb8c594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Apr 2022 18:38:23 +0200 Subject: [PATCH 08/48] Add testing for different rulesets --- .../UserInterface/TestSceneModSelectScreen.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index a5f5048961..0d568cd030 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -9,8 +9,12 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Taiko; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -68,6 +72,16 @@ namespace osu.Game.Tests.Visual.UserInterface assertCustomisationToggleState(disabled: false, active: false); } + [Test] + public void TestRulesetChange() + { + createScreen(); + AddStep("change to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo); + AddStep("change to osu!taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); + AddStep("change to osu!catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); + AddStep("change to osu!mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + } + [Test] public void TestCustomisationToggleState() { From 90e44b67ea0c883128e7e2fe11da91e2e18101e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Apr 2022 20:24:32 +0200 Subject: [PATCH 09/48] Improve robustness of test code --- .../UserInterface/TestSceneModSelectScreen.cs | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 0d568cd030..47cff40873 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -3,18 +3,16 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Catch; -using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Taiko; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -22,20 +20,29 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneModSelectScreen : OsuManualInputManagerTestScene { + [Resolved] + private RulesetStore rulesetStore { get; set; } + private ModSelectScreen modSelectScreen; [SetUpSteps] public void SetUpSteps() { + AddStep("clear contents", Clear); + AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0)); AddStep("reset mods", () => SelectedMods.SetDefault()); } - private void createScreen() => AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen + private void createScreen() { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - SelectedMods = { BindTarget = SelectedMods } - }); + AddStep("create screen", () => Child = modSelectScreen = new ModSelectScreen + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + SelectedMods = { BindTarget = SelectedMods } + }); + waitForColumnLoad(); + } [Test] public void TestStateChange() @@ -76,10 +83,10 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestRulesetChange() { createScreen(); - AddStep("change to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo); - AddStep("change to osu!taiko", () => Ruleset.Value = new TaikoRuleset().RulesetInfo); - AddStep("change to osu!catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo); - AddStep("change to osu!mania", () => Ruleset.Value = new ManiaRuleset().RulesetInfo); + changeRuleset(0); + changeRuleset(1); + changeRuleset(2); + changeRuleset(3); } [Test] @@ -113,6 +120,15 @@ namespace osu.Game.Tests.Visual.UserInterface assertCustomisationToggleState(disabled: true, active: false); // config was dismissed without explicit user action. } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", + () => modSelectScreen.ChildrenOfType().Any() && modSelectScreen.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); + + private void changeRuleset(int id) + { + AddStep($"set ruleset to {id}", () => Ruleset.Value = rulesetStore.GetRuleset(id)); + waitForColumnLoad(); + } + private void assertCustomisationToggleState(bool disabled, bool active) { ShearedToggleButton getToggle() => modSelectScreen.ChildrenOfType().Single(); From 3ba81f3fdce3791d50f526849a320919a1e9d9fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Apr 2022 15:29:03 +0900 Subject: [PATCH 10/48] Fix overlapping variable usage --- .../Visual/UserInterface/TestSceneModSelectScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 47cff40873..1e9357b6c4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); AddAssert("mod multiplier correct", () => { - double multiplier = SelectedMods.Value.Aggregate(1d, (multiplier, mod) => multiplier * mod.ScoreMultiplier); + double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); }); assertCustomisationToggleState(disabled: false, active: false); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("two panels active", () => modSelectScreen.ChildrenOfType().Count(panel => panel.Active.Value) == 2); AddAssert("mod multiplier correct", () => { - double multiplier = SelectedMods.Value.Aggregate(1d, (multiplier, mod) => multiplier * mod.ScoreMultiplier); + double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); return Precision.AlmostEquals(multiplier, modSelectScreen.ChildrenOfType().Single().Current.Value); }); assertCustomisationToggleState(disabled: false, active: false); From b5df3500079489c969ba95666a7c94c0cf977d41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Apr 2022 15:45:44 +0900 Subject: [PATCH 11/48] Adjust pop in/out transitions --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 26 +++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 659c88e070..fb991f60ba 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -38,6 +38,9 @@ namespace osu.Game.Overlays.Mods private GridContainer grid; private Container mainContent; + private PopupScreenTitle header; + private Container footer; + [BackgroundDependencyLoader] private void load() { @@ -67,7 +70,7 @@ namespace osu.Game.Overlays.Mods { new Drawable[] { - new PopupScreenTitle + header = new PopupScreenTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -96,7 +99,6 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - X = 1, Padding = new MarginPadding { Horizontal = 70 }, Children = new Drawable[] { @@ -126,7 +128,7 @@ namespace osu.Game.Overlays.Mods new[] { Empty() } } }, - new Container + footer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -269,16 +271,26 @@ namespace osu.Game.Overlays.Mods protected override void PopIn() { + const double fade_in_duration = 500; + base.PopIn(); - this.MoveToY(0, 800, Easing.OutQuint); - columnContainer.MoveToX(0, 800, Easing.OutQuint); + + header.MoveToY(0, fade_in_duration, Easing.OutQuint); + footer.MoveToY(0, fade_in_duration, Easing.OutQuint); + + this.FadeIn(fade_in_duration, Easing.OutQuint); } protected override void PopOut() { + const double fade_out_duration = 500; + base.PopOut(); - this.MoveToY(1.2f, 800, Easing.OutQuint); - columnContainer.MoveToX(1, 800, Easing.OutQuint); + + header.MoveToY(-header.DrawHeight, fade_out_duration, Easing.OutQuint); + footer.MoveToY(footer.DrawHeight, fade_out_duration, Easing.OutQuint); + + this.FadeOut(fade_out_duration, Easing.OutQuint); } private class ModColumnContainer : FillFlowContainer From bc53adb72a14e5bbc16d320243a76388d2cc063e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Apr 2022 15:50:40 +0900 Subject: [PATCH 12/48] Adjust customisation transition slightly --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index fb991f60ba..b3435affff 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -234,12 +234,14 @@ namespace osu.Game.Overlays.Mods private void updateCustomisationVisualState() { - grid.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, 800, Easing.OutQuint); + const double transition_duration = 700; + + grid.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic); float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0; - modSettingsArea.ResizeHeightTo(modAreaHeight, 800, Easing.InOutCubic); - mainContent.TransformTo(nameof(Margin), new MarginPadding { Bottom = modAreaHeight }, 800, Easing.InOutCubic); + modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic); + mainContent.TransformTo(nameof(Margin), new MarginPadding { Bottom = modAreaHeight }, transition_duration, Easing.InOutCubic); } private bool selectionBindableSyncInProgress; From 57b8c32f253f94df284fe1a9c9b4b739f9b4ac54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Apr 2022 20:42:12 +0900 Subject: [PATCH 13/48] Remove unused fields --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index b3435affff..8baf141c61 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -33,7 +33,6 @@ namespace osu.Game.Overlays.Mods private DifficultyMultiplierDisplay multiplierDisplay; private ModSettingsArea modSettingsArea; - private Container columnContainer; private FillFlowContainer columnFlow; private GridContainer grid; private Container mainContent; @@ -95,7 +94,7 @@ namespace osu.Game.Overlays.Mods }, new Drawable[] { - columnContainer = new Container + new Container { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, From 9b367d645d464bfcdeec61742de7768b928a8f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Apr 2022 20:02:47 +0200 Subject: [PATCH 14/48] Always play pop in sequence --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 8baf141c61..7915fb755c 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -29,6 +29,8 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); + protected override bool StartHidden => true; + private readonly BindableBool customisationVisible = new BindableBool(); private DifficultyMultiplierDisplay multiplierDisplay; @@ -164,7 +166,8 @@ namespace osu.Game.Overlays.Mods modSettingsArea = new ModSettingsArea { Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre + Origin = Anchor.BottomCentre, + Height = 0 } }; @@ -190,8 +193,6 @@ namespace osu.Game.Overlays.Mods } customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); - - FinishTransforms(true); } private void updateMultiplier() From 29b7460cc7454a8b4371c72f2f30858a04b146c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Apr 2022 20:05:54 +0200 Subject: [PATCH 15/48] Fix weird test step --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs index 1e9357b6c4..34d9ddcc4e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestStateChange() { createScreen(); - AddToggleStep("toggle state", visible => modSelectScreen.State.Value = visible ? Visibility.Visible : Visibility.Hidden); + AddStep("toggle state", () => modSelectScreen.ToggleVisibility()); } [Test] From 23dad7bdc4cab9bbbac3f8c707c17f2d23eb2c53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 16:39:02 +0900 Subject: [PATCH 16/48] Move scroll view padding to content level Without doing this, there is a non-masked but also non-interactive area to the left or right of the view. --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 7915fb755c..5197cb6c3e 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -100,7 +100,6 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 70 }, Children = new Drawable[] { new OsuScrollContainer(Direction.Horizontal) @@ -113,6 +112,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Horizontal = 70 }, Children = new[] { new ModColumn(ModType.DifficultyReduction, false, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), From f156cb797df13c31e43856e58037cec9b490bf7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 16:40:09 +0900 Subject: [PATCH 17/48] Improve nested scroll behaviour --- osu.Game/Overlays/Mods/ModColumn.cs | 60 ++++++++++++++++++++++- osu.Game/Overlays/Mods/ModSelectScreen.cs | 1 + 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 46a562ac98..513dd2ca3f 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -151,9 +151,10 @@ namespace osu.Game.Overlays.Mods }, new Drawable[] { - new OsuScrollContainer + new NestedVerticalScrollContainer { RelativeSizeAxes = Axes.Both, + ClampExtension = 100, ScrollbarOverlapsContent = false, Child = panelFlow = new FillFlowContainer { @@ -194,6 +195,63 @@ namespace osu.Game.Overlays.Mods } } + /// + /// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container. + /// + private class NestedVerticalScrollContainer : OsuScrollContainer + { + private OsuScrollContainer? parentScrollContainer; + + protected override void LoadComplete() + { + base.LoadComplete(); + + parentScrollContainer = findClosestParent(); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (parentScrollContainer == null) + return base.OnScroll(e); + + // If not on screen, handle scroll but also allow parent to scroll at the same time. + bool topRightOutsideOfView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight) == false; + bool bottomLeftOutsideOfView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft) == false; + + if (topRightOutsideOfView || bottomLeftOutsideOfView) + { + base.OnScroll(e); + return false; + } + + bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd(); + bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0; + + // Same deal if at one of the two extents of the view. + if (scrollingPastStart || scrollingPastEnd) + { + base.OnScroll(e); + return false; + } + + return base.OnScroll(e); + } + + // TODO: remove when https://github.com/ppy/osu-framework/pull/5092 is available. + private T? findClosestParent() where T : class, IDrawable + { + Drawable cursor = this; + + while ((cursor = cursor.Parent) != null) + { + if (cursor is T match) + return match; + } + + return default; + } + } + private void createHeaderText() { IEnumerable headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' '); diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 5197cb6c3e..1d6f784133 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = false, + ClampExtension = 100, ScrollbarOverlapsContent = false, Child = columnFlow = new ModColumnContainer { From 58e9147b12a613e6ae2dc590d82f5672304963b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 16:48:25 +0900 Subject: [PATCH 18/48] Simplify and better comment nested scroll conditionals --- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 513dd2ca3f..e915e88e99 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -214,11 +214,11 @@ namespace osu.Game.Overlays.Mods if (parentScrollContainer == null) return base.OnScroll(e); - // If not on screen, handle scroll but also allow parent to scroll at the same time. - bool topRightOutsideOfView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight) == false; - bool bottomLeftOutsideOfView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft) == false; + bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight); + bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft); - if (topRightOutsideOfView || bottomLeftOutsideOfView) + // If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view). + if (!topRightInView || !bottomLeftInView) { base.OnScroll(e); return false; @@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd(); bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0; - // Same deal if at one of the two extents of the view. + // If at either of our extents, allow the parent scroll to handle as a horizontal scroll to view more content. if (scrollingPastStart || scrollingPastEnd) { base.OnScroll(e); From 388322cd61eeec61aa7bc53597f49b4c0f463619 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 16:56:24 +0900 Subject: [PATCH 19/48] Speed up customisation panel toggle a bit --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 1d6f784133..1f0dcea6c2 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.Mods private void updateCustomisationVisualState() { - const double transition_duration = 700; + const double transition_duration = 300; grid.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic); From a408776734b8d7285d8f1af98560dae8405aeb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 17:14:59 +0900 Subject: [PATCH 20/48] Limit `FillFlow` of columns to applicable direction --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 1f0dcea6c2..8468448bbb 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -110,6 +110,7 @@ namespace osu.Game.Overlays.Mods ScrollbarOverlapsContent = false, Child = columnFlow = new ModColumnContainer { + Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Spacing = new Vector2(10, 0), From 497e5e3a36731490881e8afe3da2cd087446b197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 17:15:24 +0900 Subject: [PATCH 21/48] Slightly adjust scroll handling and also apply to `ModSettingsContainer` --- osu.Game/Overlays/Mods/ModColumn.cs | 57 ----------------- osu.Game/Overlays/Mods/ModSettingsArea.cs | 4 +- .../Mods/NestedVerticalScrollContainer.cs | 61 +++++++++++++++++++ 3 files changed, 64 insertions(+), 58 deletions(-) create mode 100644 osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index e915e88e99..8531ae1e3f 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -195,63 +195,6 @@ namespace osu.Game.Overlays.Mods } } - /// - /// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container. - /// - private class NestedVerticalScrollContainer : OsuScrollContainer - { - private OsuScrollContainer? parentScrollContainer; - - protected override void LoadComplete() - { - base.LoadComplete(); - - parentScrollContainer = findClosestParent(); - } - - protected override bool OnScroll(ScrollEvent e) - { - if (parentScrollContainer == null) - return base.OnScroll(e); - - bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight); - bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft); - - // If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view). - if (!topRightInView || !bottomLeftInView) - { - base.OnScroll(e); - return false; - } - - bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd(); - bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0; - - // If at either of our extents, allow the parent scroll to handle as a horizontal scroll to view more content. - if (scrollingPastStart || scrollingPastEnd) - { - base.OnScroll(e); - return false; - } - - return base.OnScroll(e); - } - - // TODO: remove when https://github.com/ppy/osu-framework/pull/5092 is available. - private T? findClosestParent() where T : class, IDrawable - { - Drawable cursor = this; - - while ((cursor = cursor.Parent) != null) - { - if (cursor is T match) - return match; - } - - return default; - } - } - private void createHeaderText() { IEnumerable headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' '); diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs index 9b0382c6a4..be72c1e3e3 100644 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ b/osu.Game/Overlays/Mods/ModSettingsArea.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, + ClampExtension = 100, Child = modSettingsFlow = new FillFlowContainer { AutoSizeAxes = Axes.X, @@ -157,9 +158,10 @@ namespace osu.Game.Overlays.Mods new[] { Empty() }, new Drawable[] { - new OsuScrollContainer(Direction.Vertical) + new NestedVerticalScrollContainer { RelativeSizeAxes = Axes.Both, + ClampExtension = 100, Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs new file mode 100644 index 0000000000..c62b52ef27 --- /dev/null +++ b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs @@ -0,0 +1,61 @@ +// 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 osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Mods +{ + /// + /// A scroll container that handles the case of vertically scrolling content inside a larger horizontally scrolling parent container. + /// + public class NestedVerticalScrollContainer : OsuScrollContainer + { + private OsuScrollContainer? parentScrollContainer; + + protected override void LoadComplete() + { + base.LoadComplete(); + + parentScrollContainer = findClosestParent(); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (parentScrollContainer == null) + return base.OnScroll(e); + + bool topRightInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.TopRight); + bool bottomLeftInView = parentScrollContainer.ScreenSpaceDrawQuad.Contains(ScreenSpaceDrawQuad.BottomLeft); + + // If not completely on-screen, handle scroll but also allow parent to scroll at the same time (to hopefully bring our content into full view). + if (!topRightInView || !bottomLeftInView) + return false; + + bool scrollingPastEnd = e.ScrollDelta.Y < 0 && IsScrolledToEnd(); + bool scrollingPastStart = e.ScrollDelta.Y > 0 && Target <= 0; + + // If at either of our extents, delegate scroll to the horizontal parent container. + if (scrollingPastStart || scrollingPastEnd) + return false; + + return base.OnScroll(e); + } + + // TODO: remove when https://github.com/ppy/osu-framework/pull/5092 is available. + private T? findClosestParent() where T : class, IDrawable + { + Drawable cursor = this; + + while ((cursor = cursor.Parent) != null) + { + if (cursor is T match) + return match; + } + + return default; + } + } +} From 901032bfa21e0916efa9ce1b5b184f1978f53e8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 18:25:27 +0900 Subject: [PATCH 22/48] Animate multiplier display --- .../Mods/DifficultyMultiplierDisplay.cs | 5 ++-- osu.Game/Overlays/Mods/ModSelectScreen.cs | 27 +++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 4fc3a904fa..4d3f630a55 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -20,6 +20,8 @@ namespace osu.Game.Overlays.Mods { public class DifficultyMultiplierDisplay : CompositeDrawable, IHasCurrentValue { + public const float HEIGHT = 42; + public Bindable Current { get => current.Current; @@ -42,13 +44,12 @@ namespace osu.Game.Overlays.Mods [Resolved] private OverlayColourProvider colourProvider { get; set; } - private const float height = 42; private const float multiplier_value_area_width = 56; private const float transition_duration = 200; public DifficultyMultiplierDisplay() { - Height = height; + Height = HEIGHT; AutoSizeAxes = Axes.X; InternalChild = new Container diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 8468448bbb..3437922cc8 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -84,13 +84,21 @@ namespace osu.Game.Overlays.Mods { new Container { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 100, Vertical = 10 }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + RelativePositionAxes = Axes.X, + X = 0.3f, + Height = DifficultyMultiplierDisplay.HEIGHT, + Margin = new MarginPadding + { + Horizontal = 100, + Vertical = 10 + }, Child = multiplierDisplay = new DifficultyMultiplierDisplay { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight + Anchor = Anchor.Centre, + Origin = Anchor.Centre } } }, @@ -283,6 +291,11 @@ namespace osu.Game.Overlays.Mods footer.MoveToY(0, fade_in_duration, Easing.OutQuint); this.FadeIn(fade_in_duration, Easing.OutQuint); + + multiplierDisplay + .Delay(300) + .FadeIn(200, Easing.OutQuint) + .ScaleTo(1, fade_in_duration, Easing.OutElastic); } protected override void PopOut() @@ -291,6 +304,10 @@ namespace osu.Game.Overlays.Mods base.PopOut(); + multiplierDisplay + .FadeOut(200, Easing.OutQuint) + .ScaleTo(0.75f, fade_out_duration, Easing.OutQuint); + header.MoveToY(-header.DrawHeight, fade_out_duration, Easing.OutQuint); footer.MoveToY(footer.DrawHeight, fade_out_duration, Easing.OutQuint); From 9fdeb2053746f6e0b35366cbedb4026034640ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 18:27:34 +0900 Subject: [PATCH 23/48] Animate individual `ModColumn`s during togle of oerlay --- osu.Game/Overlays/Mods/ModColumn.cs | 143 ++++++++++++---------- osu.Game/Overlays/Mods/ModSelectScreen.cs | 15 +++ 2 files changed, 91 insertions(+), 67 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 8531ae1e3f..f84ae4ac8a 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Mods { public class ModColumn : CompositeDrawable { + public readonly Container TopLevelContent; + public readonly ModType ModType; private Func? filter; @@ -78,90 +80,97 @@ namespace osu.Game.Overlays.Mods Width = 320; RelativeSizeAxes = Axes.Y; Shear = new Vector2(ModPanel.SHEAR_X, 0); - CornerRadius = ModPanel.CORNER_RADIUS; - Masking = true; Container controlContainer; InternalChildren = new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS, - Children = new Drawable[] - { - headerBackground = new Box - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS - }, - headerText = new OsuTextFlowContainer(t => - { - t.Font = OsuFont.TorusAlternate.With(size: 17); - t.Shadow = false; - t.Colour = Colour4.Black; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = new Vector2(-ModPanel.SHEAR_X, 0), - Padding = new MarginPadding - { - Horizontal = 17, - Bottom = ModPanel.CORNER_RADIUS - } - } - } - }, - new Container + TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = header_height }, - Child = contentContainer = new Container + CornerRadius = ModPanel.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, - BorderThickness = 3, - Children = new Drawable[] + new Container { - contentBackground = new Box + RelativeSizeAxes = Axes.X, + Height = header_height + ModPanel.CORNER_RADIUS, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - new GridContainer + headerBackground = new Box + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModPanel.CORNER_RADIUS + }, + headerText = new OsuTextFlowContainer(t => + { + t.Font = OsuFont.TorusAlternate.With(size: 17); + t.Shadow = false; + t.Colour = Colour4.Black; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Padding = new MarginPadding + { + Horizontal = 17, + Bottom = ModPanel.CORNER_RADIUS + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = header_height }, + Child = contentContainer = new Container { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + BorderThickness = 3, + Children = new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + contentBackground = new Box { - controlContainer = new Container - { - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 14 } - } + RelativeSizeAxes = Axes.Both }, - new Drawable[] + new GridContainer { - new NestedVerticalScrollContainer + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ClampExtension = 100, - ScrollbarOverlapsContent = false, - Child = panelFlow = new FillFlowContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 7), - Padding = new MarginPadding(7) + controlContainer = new Container + { + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 14 } + } + }, + new Drawable[] + { + new NestedVerticalScrollContainer + { + RelativeSizeAxes = Axes.Both, + ClampExtension = 100, + ScrollbarOverlapsContent = false, + Child = panelFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 7), + Padding = new MarginPadding(7) + } + } } } } diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 3437922cc8..1009c2edf6 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -106,6 +106,7 @@ namespace osu.Game.Overlays.Mods { new Container { + Depth = float.MaxValue, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Children = new Drawable[] @@ -296,6 +297,13 @@ namespace osu.Game.Overlays.Mods .Delay(300) .FadeIn(200, Easing.OutQuint) .ScaleTo(1, fade_in_duration, Easing.OutElastic); + + for (int i = 0; i < columnFlow.Count; i++) + { + columnFlow[i].TopLevelContent + .Delay(i * 30) + .MoveToY(0, fade_in_duration, Easing.OutQuint); + } } protected override void PopOut() @@ -312,6 +320,13 @@ namespace osu.Game.Overlays.Mods footer.MoveToY(footer.DrawHeight, fade_out_duration, Easing.OutQuint); this.FadeOut(fade_out_duration, Easing.OutQuint); + + for (int i = 0; i < columnFlow.Count; i++) + { + const float distance = 700; + + columnFlow[i].TopLevelContent.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint); + } } private class ModColumnContainer : FillFlowContainer From 7a1820e6bb99a48101033da2abf8a9a6cd4670fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 18:25:27 +0900 Subject: [PATCH 24/48] Fix multiplier display resetting transformations --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index 4d3f630a55..248d4f288e 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -146,8 +146,9 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); + // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. multiplierCounter.SetCountWithoutRolling(Current.Value); From 54715885af16463759bb1aa99ed70f298232bb6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 18:38:31 +0900 Subject: [PATCH 25/48] Adjust animation metrics slightly --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index 1009c2edf6..c43987ea60 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -284,25 +284,25 @@ namespace osu.Game.Overlays.Mods protected override void PopIn() { - const double fade_in_duration = 500; + const double fade_in_duration = 400; base.PopIn(); + this.FadeIn(fade_in_duration, Easing.OutQuint); header.MoveToY(0, fade_in_duration, Easing.OutQuint); footer.MoveToY(0, fade_in_duration, Easing.OutQuint); - this.FadeIn(fade_in_duration, Easing.OutQuint); - multiplierDisplay - .Delay(300) - .FadeIn(200, Easing.OutQuint) + .Delay(fade_in_duration * 0.65f) + .FadeIn(fade_in_duration / 2, Easing.OutQuint) .ScaleTo(1, fade_in_duration, Easing.OutElastic); for (int i = 0; i < columnFlow.Count; i++) { columnFlow[i].TopLevelContent .Delay(i * 30) - .MoveToY(0, fade_in_duration, Easing.OutQuint); + .MoveToY(0, fade_in_duration, Easing.OutQuint) + .FadeIn(fade_in_duration, Easing.OutQuint); } } @@ -311,21 +311,22 @@ namespace osu.Game.Overlays.Mods const double fade_out_duration = 500; base.PopOut(); + this.FadeOut(fade_out_duration, Easing.OutQuint); multiplierDisplay - .FadeOut(200, Easing.OutQuint) + .FadeOut(fade_out_duration / 2, Easing.OutQuint) .ScaleTo(0.75f, fade_out_duration, Easing.OutQuint); header.MoveToY(-header.DrawHeight, fade_out_duration, Easing.OutQuint); footer.MoveToY(footer.DrawHeight, fade_out_duration, Easing.OutQuint); - this.FadeOut(fade_out_duration, Easing.OutQuint); - for (int i = 0; i < columnFlow.Count; i++) { const float distance = 700; - columnFlow[i].TopLevelContent.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint); + columnFlow[i].TopLevelContent + .MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint) + .FadeOut(fade_out_duration, Easing.OutQuint); } } From 631aa2a6ec8e35be514953c4c4b40e35a51cdb4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 18:44:25 +0900 Subject: [PATCH 26/48] Remove left padding to allow left-most column to exist further to the.. left --- osu.Game/Overlays/Mods/ModSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectScreen.cs b/osu.Game/Overlays/Mods/ModSelectScreen.cs index c43987ea60..62080ec1b5 100644 --- a/osu.Game/Overlays/Mods/ModSelectScreen.cs +++ b/osu.Game/Overlays/Mods/ModSelectScreen.cs @@ -123,7 +123,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Horizontal = 70 }, + Margin = new MarginPadding { Right = 70 }, Children = new[] { new ModColumn(ModType.DifficultyReduction, false, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }), From 32daf64a31a09cc459461957d0aa709c52f015f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 19:23:35 +0900 Subject: [PATCH 27/48] Use newly exposed framework helper function to find closest parent --- osu.Android.props | 2 +- .../Mods/NestedVerticalScrollContainer.cs | 16 +--------------- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6a3b113fa2..a168d351fc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs index c62b52ef27..1bf965beeb 100644 --- a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs +++ b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - parentScrollContainer = findClosestParent(); + parentScrollContainer = this.FindClosestParent(); } protected override bool OnScroll(ScrollEvent e) @@ -43,19 +43,5 @@ namespace osu.Game.Overlays.Mods return base.OnScroll(e); } - - // TODO: remove when https://github.com/ppy/osu-framework/pull/5092 is available. - private T? findClosestParent() where T : class, IDrawable - { - Drawable cursor = this; - - while ((cursor = cursor.Parent) != null) - { - if (cursor is T match) - return match; - } - - return default; - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3c01f29671..e968c6602d 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 c8f170497d..e441ed81ab 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From ac799aaf7a412db957203074b9110776ad61dfbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Apr 2022 19:25:47 +0900 Subject: [PATCH 28/48] Add missing newline --- osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs index 1bf965beeb..aba47d5423 100644 --- a/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs +++ b/osu.Game/Overlays/Mods/NestedVerticalScrollContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. #nullable enable + using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; From 01da3924ccf7ccf91431ffb22db3fd15cf468b50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 11:32:35 +0900 Subject: [PATCH 29/48] Simplify `IsCurrentScreen` check to only apply to relevant call --- .../Multiplayer/MultiplayerMatchSongSelect.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 2e8f625eba..289b8730ce 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -74,18 +74,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer task.FireAndForget(onSuccess: () => Schedule(() => { + loadingLayer.Hide(); + // If an error or server side trigger occurred this screen may have already exited by external means. if (!this.IsCurrentScreen()) - return; - - loadingLayer.Hide(); - this.Exit(); + this.Exit(); }), onError: _ => Schedule(() => { - // If an error or server side trigger occurred this screen may have already exited by external means. - if (!this.IsCurrentScreen()) - return; - loadingLayer.Hide(); Carousel.AllowSelection = true; })); From f795f77cf99d5dbd902860f7e685b1d0c2130393 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 14:00:54 +0900 Subject: [PATCH 30/48] Add missing newline --- osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs index 98c593236a..4729765084 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. #nullable enable + using System; using System.Diagnostics; using System.Threading.Tasks; From 220d7bc6db4a2fbfb5c84a58c7c7058c3b7218f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 14:01:25 +0900 Subject: [PATCH 31/48] Fix dangerous realm operation in `TestSceneMultiplayerMatchSongSelect` The import process was running on the async load thread, but then accessed from the access thread later on. This seemed to somehow pass fine in headless runs, but would fail on visual test execution (specifically on `TestBeatmapConfirmed()`). --- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 381b9b58bd..84c53ce22e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; @@ -35,10 +36,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapManager manager; private RulesetStore rulesets; - private List beatmaps; + private IList beatmaps => importedBeatmapSet?.PerformRead(s => s.Beatmaps) ?? new List(); private TestMultiplayerMatchSongSelect songSelect; + private Live importedBeatmapSet; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -46,8 +49,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); - beatmaps = new List(); - var metadata = new BeatmapMetadata { Artist = "Some Artist", @@ -79,11 +80,10 @@ namespace osu.Game.Tests.Visual.Multiplayer Difficulty = new BeatmapDifficulty() }; - beatmaps.Add(beatmap); beatmapSetInfo.Beatmaps.Add(beatmap); } - manager.Import(beatmapSetInfo); + importedBeatmapSet = manager.Import(beatmapSetInfo); } public override void SetUpSteps() From 552ec5282f1d7fcb9a11cac2859f0ed570d3abde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 14:44:00 +0900 Subject: [PATCH 32/48] Change `WorkingBeatmap.GetVirtualTrack` to use length provided by `BeatmapInfo` A lot of tests are using test resources that populate the length field, but do not populate hitobjects. The general expectation is that components should be using the cached length in cases where hitobjects are not relevant, but `GetVirtualTrack` was doing its own local calculation. This could cause tests to fail due to `MusicController` changing track in the background. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 397d47c389..bb64ec796c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -17,7 +17,6 @@ using osu.Framework.Logging; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -152,24 +151,7 @@ namespace osu.Game.Beatmaps { const double excess_length = 1000; - var lastObject = Beatmap?.HitObjects.LastOrDefault(); - - double length; - - switch (lastObject) - { - case null: - length = emptyLength; - break; - - case IHasDuration endTime: - length = endTime.EndTime + excess_length; - break; - - default: - length = lastObject.StartTime + excess_length; - break; - } + double length = (BeatmapInfo?.Length + excess_length) ?? emptyLength; return audioManager.Tracks.GetVirtual(length); } From d17890ca9acd9d30ce2269dab25712cce966cbe1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 14:51:08 +0900 Subject: [PATCH 33/48] Replace a couple more local test beatmap cases which can instead use `TestResources` methods --- .../TestSceneMultiplayerMatchSongSelect.cs | 39 +------------------ .../TestSceneDeleteLocalScore.cs | 17 +------- 2 files changed, 3 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 84c53ce22e..714951cc42 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -8,12 +8,10 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Rooms; @@ -28,6 +26,7 @@ using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Multiplayer { @@ -49,41 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); - var metadata = new BeatmapMetadata - { - Artist = "Some Artist", - Title = "Some Beatmap", - Author = { Username = "Some Author" }, - }; - - var beatmapSetInfo = new BeatmapSetInfo - { - OnlineID = 10, - Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), - DateAdded = DateTimeOffset.UtcNow - }; - - for (int i = 0; i < 8; ++i) - { - int beatmapId = 10 * 10 + i; - - int length = RNG.Next(30000, 200000); - double bpm = RNG.NextSingle(80, 200); - - var beatmap = new BeatmapInfo - { - Ruleset = rulesets.GetRuleset(i % 4) ?? throw new InvalidOperationException(), - OnlineID = beatmapId, - Length = length, - BPM = bpm, - Metadata = metadata, - Difficulty = new BeatmapDifficulty() - }; - - beatmapSetInfo.Beatmaps.Add(beatmap); - } - - importedBeatmapSet = manager.Import(beatmapSetInfo); + importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 13404a9810..72dfc53c01 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.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 NUnit.Framework; @@ -17,7 +16,6 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; -using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; @@ -60,20 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), Scope = BeatmapLeaderboardScope.Local, - BeatmapInfo = new BeatmapInfo - { - ID = Guid.NewGuid(), - Metadata = new BeatmapMetadata - { - Title = "TestSong", - Artist = "TestArtist", - Author = new RealmUser - { - Username = "TestAuthor" - }, - }, - DifficultyName = "Insane" - }, + BeatmapInfo = TestResources.CreateTestBeatmapSetInfo().Beatmaps.First() } }, dialogOverlay = new DialogOverlay() From 2b8a5833ddd4794c89fbb452d6a7d2d52bb107be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 15:13:02 +0900 Subject: [PATCH 34/48] Fix back-to-front conditional check --- .../OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 289b8730ce..848424bc76 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer loadingLayer.Hide(); // If an error or server side trigger occurred this screen may have already exited by external means. - if (!this.IsCurrentScreen()) + if (this.IsCurrentScreen()) this.Exit(); }), onError: _ => Schedule(() => { From c42ef43faaeedd1ff18ccd6c1ceb16bd0e44a790 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Apr 2022 16:46:52 +0900 Subject: [PATCH 35/48] Ensure intro beatmap has protected flag set In cases this isn't set, the beatmap has likely entered a bad state. Closes https://github.com/ppy/osu/issues/17659. --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a6b54dd1f2..e569b0e517 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu bool loadThemedIntro() { - var setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == BeatmapHash); if (setInfo == null) return false; From 43724f477a1cb7870a64d0878159ad4606678d87 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 6 Apr 2022 19:14:00 +0800 Subject: [PATCH 36/48] Remove outdated NU1701 warning. --- Directory.Build.props | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5bdf12218c..3ab3dc3d13 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,14 +26,6 @@ true $(NoWarn);CS1591 - - - $(NoWarn);NU1701 - false ppy Pty Ltd From ec4f1bcbc8209dce61b5cc8ce6862a15aaa2d50c Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Wed, 6 Apr 2022 19:18:18 +0800 Subject: [PATCH 37/48] Remove outdated NETCore packages. --- osu.Desktop/osu.Desktop.csproj | 2 -- osu.Game/osu.Game.csproj | 1 - 2 files changed, 3 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index a06484214b..3ca6411812 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -25,7 +25,6 @@ - @@ -33,7 +32,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7b0f8c72c5..db1a945765 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,6 @@ - all From edb556643edf509ed77eb3da3e926658d08e45b6 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 5 Apr 2022 01:00:33 -0700 Subject: [PATCH 38/48] Add failing replay button enabled test asserts --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 8b7e1c4e58..5d25287e45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -150,10 +150,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true))); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); + AddAssert("button is enabled", () => downloadButton.ChildrenOfType().First().Enabled.Value); AddStep("delete score", () => scoreManager.Delete(imported.Value)); AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } [Test] From 4432a93d096ed81f1a94d11914c48e19c1e346dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 5 Apr 2022 01:04:19 -0700 Subject: [PATCH 39/48] Fix replay button being disabled when available locally but not online --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 6a74fdaf75..222115daca 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -87,19 +87,20 @@ namespace osu.Game.Screens.Ranking }); } - button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; - updateTooltip(); + updateState(); }, true); State.BindValueChanged(state => { button.State.Value = state.NewValue; - updateTooltip(); + updateState(); }, true); } - private void updateTooltip() + private void updateState() { + button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; + switch (replayAvailability) { case ReplayAvailability.Local: From 8a73831115ab15eff3059e938786c235646862b6 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Apr 2022 13:12:33 +0900 Subject: [PATCH 40/48] Add MP lobby 'warning' SFX for the final seconds of countdown --- .../Multiplayer/Match/MultiplayerReadyButton.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index d275f309cb..22a0243f8f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -30,13 +30,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private MultiplayerRoom room => multiplayerClient.Room; private Sample countdownTickSample; + private Sample countdownWarnSample; + private Sample countdownWarnFinalSample; [BackgroundDependencyLoader] private void load(AudioManager audio) { countdownTickSample = audio.Samples.Get(@"Multiplayer/countdown-tick"); - // disabled for now pending further work on sound effect - // countdownTickFinalSample = audio.Samples.Get(@"Multiplayer/countdown-tick-final"); + countdownWarnSample = audio.Samples.Get(@"Multiplayer/countdown-warn"); + countdownWarnFinalSample = audio.Samples.Get(@"Multiplayer/countdown-warn-final"); } protected override void LoadComplete() @@ -102,8 +104,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void playTickSound(int secondsRemaining) { if (secondsRemaining < 10) countdownTickSample?.Play(); - // disabled for now pending further work on sound effect - // if (secondsRemaining <= 3) countdownTickFinalSample?.Play(); + + if (secondsRemaining <= 3) + { + if (secondsRemaining > 0) + countdownWarnSample?.Play(); + else + countdownWarnFinalSample?.Play(); + } } private void updateButtonText() From 358931842fc1936b8141cf58d6fe2c67ca6851ce Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 6 Apr 2022 21:22:56 -0700 Subject: [PATCH 41/48] Move enabled setting to each case --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 222115daca..0c9c909395 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -99,20 +99,21 @@ namespace osu.Game.Screens.Ranking private void updateState() { - button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable; - switch (replayAvailability) { case ReplayAvailability.Local: button.TooltipText = @"watch replay"; + button.Enabled.Value = true; break; case ReplayAvailability.Online: button.TooltipText = @"download replay"; + button.Enabled.Value = true; break; default: button.TooltipText = @"replay unavailable"; + button.Enabled.Value = false; break; } } From 9ed4f310488e9c53223893674d21dea7f2a9617f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Apr 2022 13:35:07 +0900 Subject: [PATCH 42/48] 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 f1cbe1c9eb..10685d5990 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 a009307cfe..7fd2647a8a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6e35fba86f..a5efc40d51 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From b7f8716de9cd1f32c4ea9ad7f6b32b43b842167c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Apr 2022 14:10:08 +0900 Subject: [PATCH 43/48] Add test coverage of skin lookups failing when `@2x` and extension are present --- .../Skinning/LegacySkinTextureFallbackTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index 7516e7500b..db7fc0b4a0 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -69,6 +69,20 @@ namespace osu.Game.Tests.NonVisual.Skinning "Gameplay/osu/followpoint", "followpoint", 1 }, + new object[] + { + // Looking up a filename with extension specified should work. + new[] { "followpoint.png" }, + "followpoint.png", + "followpoint.png", 1 + }, + new object[] + { + // Looking up a filename with extension specified should also work with @2x sprites. + new[] { "followpoint@2x.png" }, + "followpoint.png", + "followpoint@2x.png", 2 + }, }; [TestCaseSource(nameof(fallbackTestCases))] From 205edb65a2d800eb7bf54bdf3d1b8e56f4c81f19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Apr 2022 14:16:16 +0900 Subject: [PATCH 44/48] Fix filename lookups on `LegacySkin`s going awry when extension is specified Due to the logic present to handle `@2x` fallback, the extension was potentially being added at the wrong point in the filename. This change ensures that the lookup filenames are always correct. Closes https://github.com/ppy/osu/issues/17690. --- osu.Game/Skinning/LegacySkin.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 92713023f4..a97b5d44ce 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -443,7 +443,11 @@ namespace osu.Game.Skinning string lookupName = name.Replace(@"@2x", string.Empty); float ratio = 2; - var texture = Textures?.Get(@$"{lookupName}@2x", wrapModeS, wrapModeT); + string twoTimesFilename = Path.HasExtension(lookupName) + ? @$"{Path.GetFileNameWithoutExtension(lookupName)}@2x{Path.GetExtension(lookupName)}" + : @$"{lookupName}@2x"; + + var texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); if (texture == null) { From a7e262627fe34e05c91497366b67054beb962fd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Apr 2022 15:13:39 +0900 Subject: [PATCH 45/48] Fix `DrawableStoryboardAnimation` not specifying sizing correctly Usually this would be handled by `TextureAnimation`, but because we are inheriting from `DrawableAnimation` here for reasons, we needed to implement this ourselves. Follows the implementation in `TextureAnimation`. --- .../Drawables/DrawableStoryboardAnimation.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 88cb5f40a1..2ef6f28720 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -5,8 +5,10 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Storyboards.Drawables @@ -88,6 +90,14 @@ namespace osu.Game.Storyboards.Drawables LifetimeEnd = animation.EndTime; } + protected override Vector2 GetCurrentDisplaySize() + { + Texture texture = (CurrentFrame as Sprite)?.Texture + ?? ((CurrentFrame as SkinnableSprite)?.Drawable as Sprite)?.Texture; + + return new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); + } + [BackgroundDependencyLoader] private void load(TextureStore textureStore, Storyboard storyboard) { From 5b29ddd2ed91fae0d1fbe840d4575bce6d18be7d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 15:05:08 +0300 Subject: [PATCH 46/48] Add further test coverage against paths with extensions --- .../Skinning/LegacySkinTextureFallbackTest.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index db7fc0b4a0..76c49edf78 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -83,6 +83,20 @@ namespace osu.Game.Tests.NonVisual.Skinning "followpoint.png", "followpoint@2x.png", 2 }, + new object[] + { + // Looking up a path with extension specified should work. + new[] { "Gameplay/osu/followpoint.png" }, + "Gameplay/osu/followpoint.png", + "Gameplay/osu/followpoint.png", 1 + }, + new object[] + { + // Looking up a path with extension specified should also work with @2x sprites. + new[] { "Gameplay/osu/followpoint@2x.png" }, + "Gameplay/osu/followpoint.png", + "Gameplay/osu/followpoint@2x.png", 2 + }, }; [TestCaseSource(nameof(fallbackTestCases))] From fb9fe4213df4b89ddfec65c71765322aeb38742e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 15:07:14 +0300 Subject: [PATCH 47/48] Fix skin texture lookups not handling paths with extensions --- osu.Game/Skinning/LegacySkin.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a97b5d44ce..f7d5581621 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -443,9 +443,7 @@ namespace osu.Game.Skinning string lookupName = name.Replace(@"@2x", string.Empty); float ratio = 2; - string twoTimesFilename = Path.HasExtension(lookupName) - ? @$"{Path.GetFileNameWithoutExtension(lookupName)}@2x{Path.GetExtension(lookupName)}" - : @$"{lookupName}@2x"; + string twoTimesFilename = $"{Path.ChangeExtension(lookupName, null)}@2x{Path.GetExtension(lookupName)}"; var texture = Textures?.Get(twoTimesFilename, wrapModeS, wrapModeT); From 0e9248624076d27261c872a3a763ad6051251dfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Apr 2022 22:05:04 +0900 Subject: [PATCH 48/48] Update various licence years to 2022 --- Directory.Build.props | 2 +- LICENCE | 2 +- Templates/osu.Game.Templates.csproj | 2 +- osu.Desktop/osu.nuspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 3ab3dc3d13..709545bf1d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -34,7 +34,7 @@ https://github.com/ppy/osu Automated release. ppy Pty Ltd - Copyright (c) 2021 ppy Pty Ltd + Copyright (c) 2022 ppy Pty Ltd osu game diff --git a/LICENCE b/LICENCE index b5962ad3b2..d3e7537cef 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2021 ppy Pty Ltd . +Copyright (c) 2022 ppy Pty Ltd . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj index 4624d3d771..b8c3ad373a 100644 --- a/Templates/osu.Game.Templates.csproj +++ b/Templates/osu.Game.Templates.csproj @@ -8,7 +8,7 @@ https://github.com/ppy/osu/blob/master/Templates https://github.com/ppy/osu Automated release. - Copyright (c) 2021 ppy Pty Ltd + Copyright (c) 2022 ppy Pty Ltd Templates to use when creating a ruleset for consumption in osu!. dotnet-new;templates;osu netstandard2.1 diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index 1757fd7c73..dc1ec17e2c 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -11,7 +11,7 @@ false A free-to-win rhythm game. Rhythm is just a *click* away! testing - Copyright (c) 2021 ppy Pty Ltd + Copyright (c) 2022 ppy Pty Ltd en-AU