diff --git a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs index 67dba71d45..be2a21d23d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestCasePlayerLoader.cs @@ -7,16 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Screens; -using osu.Game.Screens.Menu; using osu.Game.Screens.Play; -using osuTK; namespace osu.Game.Tests.Visual.Gameplay { public class TestCasePlayerLoader : ManualInputManagerTestCase { private PlayerLoader loader; - private readonly OsuScreenStack stack; public TestCasePlayerLoader() @@ -29,8 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Beatmap.Value = new DummyWorkingBeatmap(game); - AddStep("Reset logo position", () => logo = new OsuLogo { Position = new Vector2(0, 0) }); - AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player { AllowPause = false, @@ -58,6 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay AllowLeadIn = false, AllowResults = false, })); + + Scheduler.AddDelayed(() => slow.Ready = true, 5000); }); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); diff --git a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs index 5591bec0b8..0d4caff97e 100644 --- a/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseFacadeContainer.cs @@ -11,7 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; +using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Mods; @@ -31,7 +31,11 @@ namespace osu.Game.Tests.Visual typeof(PlayerLoader), typeof(Player), typeof(Facade), - typeof(FacadeContainer) + typeof(FacadeContainer), + typeof(ButtonSystem), + typeof(ButtonSystemState), + typeof(Menu), + typeof(MainMenu) }; [Cached] @@ -68,15 +72,10 @@ namespace osu.Game.Tests.Visual AllowPause = false, AllowLeadIn = false, AllowResults = false, + Ready = false }))); } - [Test] - public void MainMenuTest() - { - AddStep("Add new Main Menu", () => LoadScreen(new MainMenu())); - } - private class TestFacadeContainer : FacadeContainer { protected override Facade CreateFacade() => new Facade @@ -119,16 +118,23 @@ namespace osu.Game.Tests.Visual base.LogoArriving(logo, resuming); logo.FadeIn(350); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); - facadeContainer.SetLogo(logo); + facadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint); + facadeContainer.Tracking = true; moveLogoFacade(); } + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + facadeContainer.Tracking = false; + } + private void moveLogoFacade() { Random random = new Random(); if (facadeFlowComponent.Transforms.Count == 0) { - facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, 800), random.Next(0, 600)), 300); + facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300); } if (randomPositions) @@ -159,11 +165,13 @@ namespace osu.Game.Tests.Visual private class TestPlayer : Player { + public bool Ready; + [BackgroundDependencyLoader] private void load() { // Never finish loading - while (true) + while (!Ready) Thread.Sleep(1); } } diff --git a/osu.Game/Graphics/Containers/FacadeContainer.cs b/osu.Game/Graphics/Containers/FacadeContainer.cs index 611cc94958..47ba738f1c 100644 --- a/osu.Game/Graphics/Containers/FacadeContainer.cs +++ b/osu.Game/Graphics/Containers/FacadeContainer.cs @@ -10,80 +10,98 @@ using osuTK; namespace osu.Game.Graphics.Containers { + /// + /// A container that creates a to be used by its children. + /// This container also updates the position and size of the Facade, and contains logic for tracking an on the Facade's position. + /// public class FacadeContainer : Container { + protected virtual Facade CreateFacade() => new Facade(); + + public Facade Facade => facade; + + /// + /// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade. + /// + public bool Tracking; + [Cached] private Facade facade; private OsuLogo logo; + private float facadeScale; - private bool tracking; - - protected virtual Facade CreateFacade() => new Facade(); + private Vector2 startPosition; + private Easing easing; + private double startTime; + private double duration; public FacadeContainer() { facade = CreateFacade(); } - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); - - public void SetLogo(OsuLogo logo, double transformDelay = 0) + /// + /// Set the logo that should track the Facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The scale of the facade. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None) { if (logo != null) { - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); this.logo = logo; - Scheduler.AddDelayed(() => - { - tracking = true; - }, transformDelay); } + + this.facadeScale = facadeScale; + this.duration = duration; + this.easing = easing; } - private double startTime; - private double duration = 1000; - - private Vector2 startPosition; - private Easing easing = Easing.InOutExpo; + private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - if (logo == null || !tracking) + if (logo == null || !Tracking) return; - facade.Size = new Vector2(logo.SizeForFlow * 0.3f); + facade.Size = new Vector2(logo.SizeForFlow * facadeScale); if (facade.IsLoaded && logo.Position != logoTrackingPosition) { - if (logo.RelativePositionAxes != Axes.None) - { - logo.RelativePositionAxes = Axes.None; - logo.Position = logo.Parent.ToLocalSpace(logo.Position); - } + // Required for the correct position of the logo to be set with respect to logoTrackingPosition + logo.RelativePositionAxes = Axes.None; + // If this is our first update since tracking has started, initialize our starting values for interpolation if (startTime == 0) { startTime = Time.Current; + startPosition = logo.Position; } var endTime = startTime + duration; var remainingDuration = endTime - Time.Current; - if (remainingDuration <= 0) - { - remainingDuration = 0; - } + // If our transform should be instant, our position should already be at logoTrackingPosition, thus set the blend to 0. + // If we are already past when the transform should be finished playing, set the blend to 0 so that the logo is always at the position of the facade. + var blend = duration > 0 && remainingDuration > 0 + ? (float)Interpolation.ApplyEasing(easing, remainingDuration / duration) + : 0; - float currentTime = (float)Interpolation.ApplyEasing(easing, remainingDuration / duration); - logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, currentTime); + // Interpolate the position of the logo, where blend 0 is the position of the Facade, and blend 1 is where the logo was when it first began interpolating. + logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, blend); } } } } +/// +/// A placeholder container that serves as a dummy object to denote another object's location and size. +/// public class Facade : Container { -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 3df4ef9059..4c0bcd399a 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -16,6 +17,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Online.API; @@ -53,15 +55,19 @@ namespace osu.Game.Screens.Menu if (this.logo != null) { this.logo.Action = onOsuLogo; + facadeContainer.SetLogo(logo, 0.5f); // osuLogo.SizeForFlow relies on loading to be complete. buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); updateLogoState(); } + else + { + facadeContainer.Tracking = false; + } } - private readonly Drawable iconFacade; private readonly ButtonArea buttonArea; private readonly Button backButton; @@ -71,26 +77,29 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; + private readonly FacadeContainer facadeContainer; + public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = buttonArea = new ButtonArea(); + Child = facadeContainer = new FacadeContainer + { + RelativeSizeAxes = Axes.Both, + Child = buttonArea = new ButtonArea() + }; - buttonArea.AddRange(new[] + buttonArea.AddRange(new Container[] { new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - iconFacade = new Container //need a container to make the osu! icon flow properly. - { - Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT) - } + facadeContainer.Facade }); - buttonArea.Flow.CentreTarget = iconFacade; + buttonArea.Flow.CentreTarget = facadeContainer.Facade; } [Resolved(CanBeNull = true)] @@ -120,6 +129,15 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); + buttonArea.ForEach(b => + { + if (b is Button) + { + b.Origin = Anchor.CentreLeft; + b.Anchor = Anchor.CentreLeft; + } + }); + isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); @@ -247,7 +265,7 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = false; + facadeContainer.Tracking = false; game?.Toolbar.Hide(); @@ -266,19 +284,16 @@ namespace osu.Game.Screens.Menu break; case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); - logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = true; + facadeContainer.Tracking = true; if (impact) logo.Impact(); @@ -288,35 +303,17 @@ namespace osu.Game.Screens.Menu break; default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - logoTracking = true; + facadeContainer.Tracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; case ButtonSystemState.EnteringMode: - logoTracking = true; + facadeContainer.Tracking = true; break; } } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); - - private bool logoTracking; - - protected override void Update() - { - base.Update(); - - if (logo != null) - { - if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded) - logo.Position = logoTrackingPosition; - - iconFacade.Width = logo.SizeForFlow * 0.5f; - } - } } public enum ButtonSystemState diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index b7155f771f..f35eb6979d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play private Player player; - private FacadeContainer content; + private FacadeContainer facadeContainer; protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer(); @@ -62,11 +62,11 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - InternalChild = content = CreateFacadeContainer(); - content.Anchor = Anchor.Centre; - content.Origin = Anchor.Centre; - content.RelativeSizeAxes = Axes.Both; - content.Children = new Drawable[] + InternalChild = facadeContainer = CreateFacadeContainer(); + facadeContainer.Anchor = Anchor.Centre; + facadeContainer.Origin = Anchor.Centre; + facadeContainer.RelativeSizeAxes = Axes.Both; + facadeContainer.Children = new Drawable[] { info = new BeatmapMetadataDisplay(Beatmap.Value) { @@ -122,21 +122,21 @@ namespace osu.Game.Screens.Play private void contentIn() { - content.ScaleTo(1, 650, Easing.OutQuint); - content.FadeInFromZero(400); + facadeContainer.ScaleTo(1, 650, Easing.OutQuint); + facadeContainer.FadeInFromZero(400); } private void contentOut() { - content.ScaleTo(0.7f, 300, Easing.InQuint); - content.FadeOut(250); + facadeContainer.ScaleTo(0.7f, 300, Easing.InQuint); + facadeContainer.FadeOut(250); } public override void OnEntering(IScreen last) { base.OnEntering(last); - content.ScaleTo(0.7f); + facadeContainer.ScaleTo(0.7f); Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -155,7 +155,15 @@ namespace osu.Game.Screens.Play logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.FadeIn(350); - content.SetLogo(logo, duration); + facadeContainer.SetLogo(logo, 0.3f, 500, Easing.InOutQuint); + + Scheduler.AddDelayed(() => facadeContainer.Tracking = true, duration); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + facadeContainer.Tracking = false; } protected override void LoadComplete() @@ -230,7 +238,7 @@ namespace osu.Game.Screens.Play public override bool OnExiting(IScreen next) { - content.ScaleTo(0.7f, 150, Easing.InQuint); + facadeContainer.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); cancelLoad(); @@ -305,7 +313,6 @@ namespace osu.Game.Screens.Play private LoadingAnimation loading; private Sprite backgroundSprite; private ModDisplay modDisplay; - private FillFlowContainer fillFlowContainer; public bool Loading { @@ -340,7 +347,7 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Both; Children = new Drawable[] { - fillFlowContainer = new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre,