diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs index 6a41d08f01..2eb5a8014e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -9,47 +10,96 @@ using osuTK; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneOsuAnimatedButton : OsuGridTestScene + public class TestSceneOsuAnimatedButton : OsuTestScene { - public TestSceneOsuAnimatedButton() - : base(3, 2) + [Test] + public void TestRelativeSized() { - Cell(0).Add(new BaseContainer("relative sized") + AddStep("add button", () => Child = new BaseContainer("relative sized") { RelativeSizeAxes = Axes.Both, + Action = () => { } }); + } - Cell(1).Add(new BaseContainer("auto sized") + [Test] + public void TestAutoSized() + { + AddStep("add button", () => Child = new BaseContainer("auto sized") { - AutoSizeAxes = Axes.Both + AutoSizeAxes = Axes.Both, + Action = () => { } }); + } - Cell(2).Add(new BaseContainer("relative Y auto X") + [Test] + public void TestRelativeYAutoX() + { + AddStep("add button", () => Child = new BaseContainer("relative Y auto X") { RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X + AutoSizeAxes = Axes.X, + Action = () => { } }); + } - Cell(3).Add(new BaseContainer("relative X auto Y") + [Test] + public void TestRelativeXAutoY() + { + AddStep("add button", () => Child = new BaseContainer("relative X auto Y") { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + Action = () => { } }); + } - Cell(4).Add(new BaseContainer("fixed") + [Test] + public void TestFixed1() + { + AddStep("add button", () => Child = new BaseContainer("fixed") { Size = new Vector2(100), + Action = () => { } }); + } - Cell(5).Add(new BaseContainer("fixed") + [Test] + public void TestFixed2() + { + AddStep("add button", () => Child = new BaseContainer("fixed") { Size = new Vector2(100, 50), + Action = () => { } + }); + } + + [Test] + public void TestToggleEnabled() + { + BaseContainer button = null; + + AddStep("add button", () => Child = button = new BaseContainer("fixed") + { + Size = new Vector2(200), }); AddToggleStep("toggle enabled", toggle => { for (int i = 0; i < 6; i++) - ((BaseContainer)Cell(i).Child).Action = toggle ? () => { } : (Action)null; + button.Action = toggle ? () => { } : (Action)null; + }); + } + + [Test] + public void TestInitiallyDisabled() + { + AddStep("add disabled button", () => + { + Child = new BaseContainer("disabled") + { + Size = new Vector2(100) + }; }); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs new file mode 100644 index 0000000000..9d086cce5c --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs @@ -0,0 +1,46 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuButton : OsuTestScene + { + [Test] + public void TestToggleEnabled() + { + OsuButton button = null; + + AddStep("add button", () => Child = button = new OsuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200), + Text = "Button" + }); + + AddToggleStep("toggle enabled", toggle => + { + for (int i = 0; i < 6; i++) + button.Action = toggle ? () => { } : (Action)null; + }); + } + + [Test] + public void TestInitiallyDisabled() + { + AddStep("add button", () => Child = new OsuButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200), + Text = "Button" + }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 70a107ca04..13b42c0f13 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -38,6 +38,9 @@ namespace osu.Game.Graphics.UserInterface } } + [Resolved] + private OsuColour colours { get; set; } + protected override Container Content => content; private readonly Container content; @@ -73,17 +76,25 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { if (AutoSizeAxes != Axes.None) { content.RelativeSizeAxes = (Axes.Both & ~AutoSizeAxes); content.AutoSizeAxes = AutoSizeAxes; } - - Enabled.BindValueChanged(enabled => this.FadeColour(enabled.NewValue ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Colour = dimColour; + Enabled.BindValueChanged(_ => this.FadeColour(dimColour, 200, Easing.OutQuint)); + } + + private Color4 dimColour => Enabled.Value ? Color4.White : colours.Gray9; + protected override bool OnHover(HoverEvent e) { hover.FadeIn(500, Easing.OutQuint); diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 82a3e73b84..7c1e8d90a0 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -85,8 +84,6 @@ namespace osu.Game.Graphics.UserInterface if (hoverSounds.HasValue) AddInternal(new HoverClickSounds(hoverSounds.Value)); - - Enabled.BindValueChanged(enabledChanged, true); } [BackgroundDependencyLoader] @@ -94,11 +91,18 @@ namespace osu.Game.Graphics.UserInterface { if (backgroundColour == null) BackgroundColour = colours.BlueDark; - - Enabled.ValueChanged += enabledChanged; - Enabled.TriggerChange(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Colour = dimColour; + Enabled.BindValueChanged(_ => this.FadeColour(dimColour, 200, Easing.OutQuint)); + } + + private Color4 dimColour => Enabled.Value ? Color4.White : Color4.Gray; + protected override bool OnClick(ClickEvent e) { if (Enabled.Value) @@ -144,10 +148,5 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold) }; - - private void enabledChanged(ValueChangedEvent e) - { - this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); - } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs index e136627d43..28c9bef3f0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs @@ -15,6 +15,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + protected override void LoadComplete() + { + base.LoadComplete(); + + client.RoomUpdated += onRoomUpdated; + onRoomUpdated(); + } + + private void onRoomUpdated() + { + if (client.Room == null) + return; + + Debug.Assert(client.LocalUser != null); + + // If the user exits gameplay before score submission completes, we'll transition to idle when results has been prepared. + if (client.LocalUser.State == MultiplayerUserState.Results && this.IsCurrentScreen()) + transitionFromResults(); + } + public override void OnResuming(IScreen last) { base.OnResuming(last); @@ -22,23 +42,27 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.Room == null) return; + if (!(last is MultiplayerPlayerLoader playerLoader)) + return; + + // If gameplay wasn't finished, then we have a simple path back to the idle state by aborting gameplay. + if (!playerLoader.GameplayPassed) + { + client.AbortGameplay(); + return; + } + + // If gameplay was completed and the user went all the way to results, we'll transition to idle here. + // Otherwise, the transition will happen in onRoomUpdated(). + transitionFromResults(); + } + + private void transitionFromResults() + { Debug.Assert(client.LocalUser != null); - switch (client.LocalUser.State) - { - case MultiplayerUserState.Spectating: - break; - - case MultiplayerUserState.WaitingForLoad: - case MultiplayerUserState.Loaded: - case MultiplayerUserState.Playing: - client.AbortGameplay(); - break; - - default: - client.ChangeState(MultiplayerUserState.Idle); - break; - } + if (client.LocalUser.State == MultiplayerUserState.Results) + client.ChangeState(MultiplayerUserState.Idle); } protected override string ScreenTitle => "Multiplayer"; @@ -46,5 +70,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager(); protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (client != null) + client.RoomUpdated -= onRoomUpdated; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index af83543b16..c4dd200614 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -30,7 +30,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osuTK; @@ -478,7 +477,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray()); default: - return new PlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); + return new MultiplayerPlayerLoader(() => new MultiplayerPlayer(Room, SelectedItem.Value, users)); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs new file mode 100644 index 0000000000..470ba59a76 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Screens; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer +{ + public class MultiplayerPlayerLoader : PlayerLoader + { + public bool GameplayPassed => player?.GameplayPassed == true; + + private Player player; + + public MultiplayerPlayerLoader(Func createPlayer) + : base(createPlayer) + { + } + + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + player = (Player)next; + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 745e1f9e7c..77a6b27114 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -66,6 +66,11 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; + /// + /// Whether gameplay has completed without the user having failed. + /// + public bool GameplayPassed { get; private set; } + public Action RestartRequested; public bool HasFailed { get; private set; } @@ -666,6 +671,7 @@ namespace osu.Game.Screens.Play resultsDisplayDelegate?.Cancel(); resultsDisplayDelegate = null; + GameplayPassed = false; ValidForResume = true; skipOutroOverlay.Hide(); return; @@ -675,6 +681,8 @@ namespace osu.Game.Screens.Play if (HealthProcessor.HasFailed) return; + GameplayPassed = true; + // Setting this early in the process means that even if something were to go wrong in the order of events following, there // is no chance that a user could return to the (already completed) Player instance from a child screen. ValidForResume = false;