From a2794922d53a5db8f2c2748c535d2fc441b6621f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 06:23:07 +0300 Subject: [PATCH 1/4] Add `TopLevelContent` layer for applying external transforms on footer buttons --- .../TestSceneScreenFooterButtonMods.cs | 15 +- osu.Game/Screens/Footer/ScreenFooterButton.cs | 143 ++++++++++-------- .../SelectV2/Footer/ScreenFooterButtonMods.cs | 8 +- 3 files changed, 99 insertions(+), 67 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs index df2109ace8..2e2baf6e96 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooterButtonMods.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.SelectV2.Footer; @@ -26,12 +27,12 @@ namespace osu.Game.Tests.Visual.UserInterface public TestSceneScreenFooterButtonMods() { - Add(footerButtonMods = new TestScreenFooterButtonMods + Add(footerButtonMods = new TestScreenFooterButtonMods(new TestModSelectOverlay()) { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - X = -100, Action = () => { }, + X = -100, }); } @@ -112,9 +113,19 @@ namespace osu.Game.Tests.Visual.UserInterface return expectedValue == footerButtonMods.MultiplierText.Current.Value; } + private partial class TestModSelectOverlay : UserModSelectOverlay + { + protected override bool ShowPresets => true; + } + private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; + + public TestScreenFooterButtonMods(ModSelectOverlay overlay) + : base(overlay) + { + } } } } diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 8fbd9f6cba..dda95d1d4c 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Footer set => icon.Icon = value; } - protected LocalisableString Text + public LocalisableString Text { set => text.Text = value; } @@ -69,87 +69,99 @@ namespace osu.Game.Screens.Footer private readonly Box glowBox; private readonly Box flashLayer; - public ScreenFooterButton() + public readonly Container TopLevelContent; + public readonly OverlayContainer? Overlay; + + public ScreenFooterButton(OverlayContainer? overlay = null) { + Overlay = overlay; + Size = new Vector2(BUTTON_WIDTH, BUTTON_HEIGHT); - Child = new Container + Child = TopLevelContent = new Container { - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 4, - // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. - Colour = Colour4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - }, - Shear = BUTTON_SHEAR, - Masking = true, - CornerRadius = CORNER_RADIUS, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - backgroundBox = new Box - { - RelativeSizeAxes = Axes.Both - }, - glowBox = new Box - { - RelativeSizeAxes = Axes.Both - }, - // For elements that should not be sheared. new Container { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = -BUTTON_SHEAR, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 4, + // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. + Colour = Colour4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + }, + Shear = BUTTON_SHEAR, + Masking = true, + CornerRadius = CORNER_RADIUS, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - TextContainer = new Container + backgroundBox = new Box { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 42, - AutoSizeAxes = Axes.Both, - Child = text = new OsuSpriteText + RelativeSizeAxes = Axes.Both + }, + glowBox = new Box + { + RelativeSizeAxes = Axes.Both + }, + // For elements that should not be sheared. + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = -BUTTON_SHEAR, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - // figma design says the size is 16, but due to the issues with font sizes 19 matches better - Font = OsuFont.TorusAlternate.With(size: 19), - AlwaysPresent = true + TextContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 42, + AutoSizeAxes = Axes.Both, + Child = text = new OsuSpriteText + { + // figma design says the size is 16, but due to the issues with font sizes 19 matches better + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true + } + }, + icon = new SpriteIcon + { + Y = 12, + Size = new Vector2(20), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, } }, - icon = new SpriteIcon + new Container { - Y = 12, - Size = new Vector2(20), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre + Shear = -BUTTON_SHEAR, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Y = -CORNER_RADIUS, + Size = new Vector2(120, 6), + Masking = true, + CornerRadius = 3, + Child = bar = new Box + { + RelativeSizeAxes = Axes.Both, + } }, - } - }, - new Container - { - Shear = -BUTTON_SHEAR, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Y = -CORNER_RADIUS, - Size = new Vector2(120, 6), - Masking = true, - CornerRadius = 3, - Child = bar = new Box - { - RelativeSizeAxes = Axes.Both, - } - }, - flashLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White.Opacity(0.9f), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White.Opacity(0.9f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }, + } + } }; } @@ -157,6 +169,9 @@ namespace osu.Game.Screens.Footer { base.LoadComplete(); + if (Overlay != null) + OverlayState.BindTo(Overlay.State); + OverlayState.BindValueChanged(_ => updateDisplay()); Enabled.BindValueChanged(_ => updateDisplay(), true); diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index f590a19164..4df4116de1 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Footer; using osu.Game.Screens.Play.HUD; @@ -59,6 +60,11 @@ namespace osu.Game.Screens.SelectV2.Footer [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public ScreenFooterButtonMods(ModSelectOverlay overlay) + : base(overlay) + { + } + [BackgroundDependencyLoader] private void load() { @@ -66,7 +72,7 @@ namespace osu.Game.Screens.SelectV2.Footer Icon = FontAwesome.Solid.ExchangeAlt; AccentColour = colours.Lime1; - AddRange(new[] + TopLevelContent.AddRange(new[] { unrankedBadge = new UnrankedBadge(), modDisplayBar = new Container From d0e8632fbffef00b75cc227e1435c03ff348c467 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 06:24:58 +0300 Subject: [PATCH 2/4] Refactor `ScreenFooter` to prepare for global usage and add transitions --- osu.Game/Screens/Footer/ScreenFooter.cs | 162 +++++++++++++++++++----- 1 file changed, 130 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 4a10a4cdab..d299bf7362 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -2,54 +2,33 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Footer { - public partial class ScreenFooter : InputBlockingContainer + public partial class ScreenFooter : OverlayContainer { - private const int height = 60; private const int padding = 60; + private const float delay_per_button = 30; + + public const int HEIGHT = 60; private readonly List overlays = new List(); - /// The button to be added. - /// The to be toggled by this button. - public void AddButton(ScreenFooterButton button, OverlayContainer? overlay = null) - { - if (overlay != null) - { - overlays.Add(overlay); - button.Action = () => showOverlay(overlay); - button.OverlayState.BindTo(overlay.State); - } - - buttons.Add(button); - } - - private void showOverlay(OverlayContainer overlay) - { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - } - - private FillFlowContainer buttons = null!; + private FillFlowContainer buttonsFlow = null!; + private Container removedButtonsContainer = null!; public ScreenFooter() { RelativeSizeAxes = Axes.X; - Height = height; + Height = HEIGHT; Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; } @@ -64,9 +43,10 @@ namespace osu.Game.Screens.Footer RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5 }, - buttons = new FillFlowContainer + buttonsFlow = new FillFlowContainer { - Position = new Vector2(ScreenBackButton.BUTTON_WIDTH + padding, 10), + Margin = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding }, + Y = 10f, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, @@ -80,7 +60,125 @@ namespace osu.Game.Screens.Footer Origin = Anchor.BottomLeft, Action = () => { }, }, + removedButtonsContainer = new Container + { + Margin = new MarginPadding { Left = 12f + ScreenBackButton.BUTTON_WIDTH + padding }, + Y = 10f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + }, }; } + + protected override void PopIn() + { + this.MoveToY(0, 400, Easing.OutQuint) + .FadeIn(400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.MoveToY(HEIGHT, 400, Easing.OutQuint) + .FadeOut(400, Easing.OutQuint); + } + + public void SetButtons(IReadOnlyList buttons) + { + overlays.Clear(); + + var oldButtons = buttonsFlow.ToArray(); + + for (int i = 0; i < oldButtons.Length; i++) + { + var oldButton = oldButtons[i]; + + buttonsFlow.Remove(oldButton, false); + removedButtonsContainer.Add(oldButton); + + if (buttons.Count > 0) + fadeButtonToLeft(oldButton, i, oldButtons.Length); + else + fadeButtonToBottom(oldButton, i, oldButtons.Length); + + Scheduler.AddDelayed(() => oldButton.Expire(), oldButton.TopLevelContent.LatestTransformEndTime - Time.Current); + } + + for (int i = 0; i < buttons.Count; i++) + { + var newButton = buttons[i]; + + if (newButton.Overlay != null) + { + newButton.Action = () => showOverlay(newButton.Overlay); + overlays.Add(newButton.Overlay); + } + + Debug.Assert(!newButton.IsLoaded); + buttonsFlow.Add(newButton); + + int index = i; + + // ensure transforms are added after LoadComplete to not be aborted by the FinishTransforms call. + newButton.OnLoadComplete += _ => + { + if (oldButtons.Length > 0) + fadeButtonFromRight(newButton, index, buttons.Count, 240); + else + fadeButtonFromBottom(newButton, index); + }; + } + } + + private void fadeButtonFromRight(ScreenFooterButton button, int index, int count, float startDelay) + { + button.TopLevelContent + .MoveToX(-300f) + .FadeOut(); + + button.TopLevelContent + .Delay(startDelay + (count - index) * delay_per_button) + .MoveToX(0f, 240, Easing.OutCubic) + .FadeIn(240, Easing.OutCubic); + } + + private void fadeButtonFromBottom(ScreenFooterButton button, int index) + { + button.TopLevelContent + .MoveToY(100f) + .FadeOut(); + + button.TopLevelContent + .Delay(index * delay_per_button) + .MoveToY(0f, 240, Easing.OutCubic) + .FadeIn(240, Easing.OutCubic); + } + + private void fadeButtonToLeft(ScreenFooterButton button, int index, int count) + { + button.TopLevelContent + .Delay((count - index) * delay_per_button) + .FadeOut(240, Easing.InOutCubic) + .MoveToX(300f, 360, Easing.InOutCubic); + } + + private void fadeButtonToBottom(ScreenFooterButton button, int index, int count) + { + button.TopLevelContent + .Delay((count - index) * delay_per_button) + .FadeOut(240, Easing.InOutCubic) + .MoveToY(100f, 240, Easing.InOutCubic); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) + { + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } + } } } From 95840672cb945b5f7a3ca3039b0dea8138d7ddcc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 06:26:59 +0300 Subject: [PATCH 3/4] Clean up screen footer test scene --- .../UserInterface/TestSceneScreenFooter.cs | 180 ++++-------------- 1 file changed, 39 insertions(+), 141 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index 162609df70..01b3aa5787 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -2,34 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Footer; using osu.Game.Screens.SelectV2.Footer; -using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene { - private ScreenFooterButtonRandom randomButton = null!; - private ScreenFooterButtonMods modsButton = null!; - - private bool nextRandomCalled; - private bool previousRandomCalled; - - private DummyOverlay overlay = null!; + private ScreenFooter screenFooter = null!; + private TestModSelectOverlay overlay = null!; [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -37,160 +25,70 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void SetUp() => Schedule(() => { - nextRandomCalled = false; - previousRandomCalled = false; - - ScreenFooter footer; - Children = new Drawable[] { + overlay = new TestModSelectOverlay + { + Padding = new MarginPadding + { + Bottom = ScreenFooter.HEIGHT + } + }, new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = footer = new ScreenFooter(), + Child = screenFooter = new ScreenFooter(), }, - overlay = new DummyOverlay() }; - footer.AddButton(modsButton = new ScreenFooterButtonMods { Current = SelectedMods }, overlay); - footer.AddButton(randomButton = new ScreenFooterButtonRandom + screenFooter.SetButtons(new ScreenFooterButton[] { - NextRandom = () => nextRandomCalled = true, - PreviousRandom = () => previousRandomCalled = true + new ScreenFooterButtonMods(overlay) { Current = SelectedMods }, + new ScreenFooterButtonRandom(), + new ScreenFooterButtonOptions(), }); - footer.AddButton(new ScreenFooterButtonOptions()); - - overlay.Hide(); }); [SetUpSteps] public void SetUpSteps() { - AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); - AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo))); + AddStep("show footer", () => screenFooter.Show()); } + /// + /// Transition when moving from a screen with no buttons to a screen with buttons. + /// [Test] - public void TestMods() + public void TestButtonsIn() { - AddStep("one mod", () => SelectedMods.Value = new List { new OsuModHidden() }); - AddStep("two mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock() }); - AddStep("three mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime() }); - AddStep("four mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic() }); - AddStep("five mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() }); - - AddStep("modified", () => SelectedMods.Value = new List { new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + one", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + two", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + three", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - AddStep("modified + four", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModClassic(), new OsuModDifficultyAdjust(), new OsuModDoubleTime { SpeedChange = { Value = 1.2 } } }); - - AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); - AddWaitStep("wait", 3); - AddStep("one mod", () => SelectedMods.Value = new List { new OsuModHidden() }); - - AddStep("clear mods", () => SelectedMods.Value = Array.Empty()); - AddWaitStep("wait", 3); - AddStep("five mods", () => SelectedMods.Value = new List { new OsuModHidden(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModClassic(), new OsuModDifficultyAdjust() }); } + /// + /// Transition when moving from a screen with buttons to a screen with no buttons. + /// [Test] - public void TestShowOptions() + public void TestButtonsOut() { - AddStep("enable options", () => + AddStep("clear buttons", () => screenFooter.SetButtons(Array.Empty())); + } + + /// + /// Transition when moving from a screen with buttons to a screen with buttons. + /// + [Test] + public void TestReplaceButtons() + { + AddStep("replace buttons", () => screenFooter.SetButtons(new[] { - var optionsButton = this.ChildrenOfType().Last(); - - optionsButton.Enabled.Value = true; - optionsButton.TriggerClick(); - }); + new ScreenFooterButton { Text = "One", Action = () => { } }, + new ScreenFooterButton { Text = "Two", Action = () => { } }, + new ScreenFooterButton { Text = "Three", Action = () => { } }, + })); } - [Test] - public void TestState() + private partial class TestModSelectOverlay : UserModSelectOverlay { - AddToggleStep("set options enabled state", state => this.ChildrenOfType().Last().Enabled.Value = state); - } - - [Test] - public void TestFooterRandom() - { - AddStep("press F2", () => InputManager.Key(Key.F2)); - AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled); - } - - [Test] - public void TestFooterRandomViaMouse() - { - AddStep("click button", () => - { - InputManager.MoveMouseTo(randomButton); - InputManager.Click(MouseButton.Left); - }); - AddAssert("next random invoked", () => nextRandomCalled && !previousRandomCalled); - } - - [Test] - public void TestFooterRewind() - { - AddStep("press Shift+F2", () => - { - InputManager.PressKey(Key.LShift); - InputManager.PressKey(Key.F2); - InputManager.ReleaseKey(Key.F2); - InputManager.ReleaseKey(Key.LShift); - }); - AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); - } - - [Test] - public void TestFooterRewindViaShiftMouseLeft() - { - AddStep("shift + click button", () => - { - InputManager.PressKey(Key.LShift); - InputManager.MoveMouseTo(randomButton); - InputManager.Click(MouseButton.Left); - InputManager.ReleaseKey(Key.LShift); - }); - AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); - } - - [Test] - public void TestFooterRewindViaMouseRight() - { - AddStep("right click button", () => - { - InputManager.MoveMouseTo(randomButton); - InputManager.Click(MouseButton.Right); - }); - AddAssert("previous random invoked", () => previousRandomCalled && !nextRandomCalled); - } - - [Test] - public void TestOverlayPresent() - { - AddStep("Press F1", () => - { - InputManager.MoveMouseTo(modsButton); - InputManager.Click(MouseButton.Left); - }); - AddAssert("Overlay visible", () => overlay.State.Value == Visibility.Visible); - AddStep("Hide", () => overlay.Hide()); - } - - private partial class DummyOverlay : ShearedOverlayContainer - { - public DummyOverlay() - : base(OverlayColourScheme.Green) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Header.Title = "An overlay"; - } + protected override bool ShowPresets => true; } } } From f59d94bba4171a0dff65be3b10f3c9576f00795b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 7 Jun 2024 22:07:37 +0300 Subject: [PATCH 4/4] Move transitions inside `ScreenFooterButton` and re-use `Content` from base implementation instead The point is to apply the transitions against a container that's inside of `ScreenFooterButton`, because the `ScreenFooterButton` drawable's position is being controlled by the flow container it's contained within, and we cannot apply the transitions on it directly. --- osu.Game/Screens/Footer/ScreenFooter.cs | 54 ++---- osu.Game/Screens/Footer/ScreenFooterButton.cs | 173 +++++++++++------- .../SelectV2/Footer/ScreenFooterButtonMods.cs | 2 +- 3 files changed, 115 insertions(+), 114 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index d299bf7362..9e0f657e8b 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -97,11 +97,9 @@ namespace osu.Game.Screens.Footer removedButtonsContainer.Add(oldButton); if (buttons.Count > 0) - fadeButtonToLeft(oldButton, i, oldButtons.Length); + makeButtonDisappearToRightAndExpire(oldButton, i, oldButtons.Length); else - fadeButtonToBottom(oldButton, i, oldButtons.Length); - - Scheduler.AddDelayed(() => oldButton.Expire(), oldButton.TopLevelContent.LatestTransformEndTime - Time.Current); + makeButtonDisappearToBottomAndExpire(oldButton, i, oldButtons.Length); } for (int i = 0; i < buttons.Count; i++) @@ -123,52 +121,24 @@ namespace osu.Game.Screens.Footer newButton.OnLoadComplete += _ => { if (oldButtons.Length > 0) - fadeButtonFromRight(newButton, index, buttons.Count, 240); + makeButtonAppearFromLeft(newButton, index, buttons.Count, 240); else - fadeButtonFromBottom(newButton, index); + makeButtonAppearFromBottom(newButton, index); }; } } - private void fadeButtonFromRight(ScreenFooterButton button, int index, int count, float startDelay) - { - button.TopLevelContent - .MoveToX(-300f) - .FadeOut(); + private void makeButtonAppearFromLeft(ScreenFooterButton button, int index, int count, float startDelay) + => button.AppearFromLeft(startDelay + (count - index) * delay_per_button); - button.TopLevelContent - .Delay(startDelay + (count - index) * delay_per_button) - .MoveToX(0f, 240, Easing.OutCubic) - .FadeIn(240, Easing.OutCubic); - } + private void makeButtonAppearFromBottom(ScreenFooterButton button, int index) + => button.AppearFromBottom(index * delay_per_button); - private void fadeButtonFromBottom(ScreenFooterButton button, int index) - { - button.TopLevelContent - .MoveToY(100f) - .FadeOut(); + private void makeButtonDisappearToRightAndExpire(ScreenFooterButton button, int index, int count) + => button.DisappearToRightAndExpire((count - index) * delay_per_button); - button.TopLevelContent - .Delay(index * delay_per_button) - .MoveToY(0f, 240, Easing.OutCubic) - .FadeIn(240, Easing.OutCubic); - } - - private void fadeButtonToLeft(ScreenFooterButton button, int index, int count) - { - button.TopLevelContent - .Delay((count - index) * delay_per_button) - .FadeOut(240, Easing.InOutCubic) - .MoveToX(300f, 360, Easing.InOutCubic); - } - - private void fadeButtonToBottom(ScreenFooterButton button, int index, int count) - { - button.TopLevelContent - .Delay((count - index) * delay_per_button) - .FadeOut(240, Easing.InOutCubic) - .MoveToY(100f, 240, Easing.InOutCubic); - } + private void makeButtonDisappearToBottomAndExpire(ScreenFooterButton button, int index, int count) + => button.DisappearToBottomAndExpire((count - index) * delay_per_button); private void showOverlay(OverlayContainer overlay) { diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index dda95d1d4c..1e5576e47a 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -69,7 +69,6 @@ namespace osu.Game.Screens.Footer private readonly Box glowBox; private readonly Box flashLayer; - public readonly Container TopLevelContent; public readonly OverlayContainer? Overlay; public ScreenFooterButton(OverlayContainer? overlay = null) @@ -78,89 +77,85 @@ namespace osu.Game.Screens.Footer Size = new Vector2(BUTTON_WIDTH, BUTTON_HEIGHT); - Child = TopLevelContent = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - new Container + EdgeEffect = new EdgeEffectParameters { - EdgeEffect = new EdgeEffectParameters + Type = EdgeEffectType.Shadow, + Radius = 4, + // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. + Colour = Colour4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + }, + Shear = BUTTON_SHEAR, + Masking = true, + CornerRadius = CORNER_RADIUS, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + backgroundBox = new Box { - Type = EdgeEffectType.Shadow, - Radius = 4, - // Figma says 50% opacity, but it does not match up visually if taken at face value, and looks bad. - Colour = Colour4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), + RelativeSizeAxes = Axes.Both }, - Shear = BUTTON_SHEAR, - Masking = true, - CornerRadius = CORNER_RADIUS, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + glowBox = new Box { - backgroundBox = new Box + RelativeSizeAxes = Axes.Both + }, + // For elements that should not be sheared. + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = -BUTTON_SHEAR, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - glowBox = new Box - { - RelativeSizeAxes = Axes.Both - }, - // For elements that should not be sheared. - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = -BUTTON_SHEAR, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + TextContainer = new Container { - TextContainer = new Container + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 42, + AutoSizeAxes = Axes.Both, + Child = text = new OsuSpriteText { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 42, - AutoSizeAxes = Axes.Both, - Child = text = new OsuSpriteText - { - // figma design says the size is 16, but due to the issues with font sizes 19 matches better - Font = OsuFont.TorusAlternate.With(size: 19), - AlwaysPresent = true - } - }, - icon = new SpriteIcon - { - Y = 12, - Size = new Vector2(20), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - } - }, - new Container - { - Shear = -BUTTON_SHEAR, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Y = -CORNER_RADIUS, - Size = new Vector2(120, 6), - Masking = true, - CornerRadius = 3, - Child = bar = new Box + // figma design says the size is 16, but due to the issues with font sizes 19 matches better + Font = OsuFont.TorusAlternate.With(size: 19), + AlwaysPresent = true + } + }, + icon = new SpriteIcon { - RelativeSizeAxes = Axes.Both, - } - }, - flashLayer = new Box + Y = 12, + Size = new Vector2(20), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + } + }, + new Container + { + Shear = -BUTTON_SHEAR, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Y = -CORNER_RADIUS, + Size = new Vector2(120, 6), + Masking = true, + CornerRadius = 3, + Child = bar = new Box { RelativeSizeAxes = Axes.Both, - Colour = Colour4.White.Opacity(0.9f), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, + } }, - } + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White.Opacity(0.9f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }, } }; } @@ -230,5 +225,41 @@ namespace osu.Game.Screens.Footer glowBox.FadeColour(ColourInfo.GradientVertical(buttonAccentColour.Opacity(0f), buttonAccentColour.Opacity(0.2f)), 150, Easing.OutQuint); } + + public void AppearFromLeft(double delay) + { + Content.MoveToX(-300f) + .FadeOut() + .Delay(delay) + .MoveToX(0f, 240, Easing.OutCubic) + .FadeIn(240, Easing.OutCubic); + } + + public void AppearFromBottom(double delay) + { + Content.MoveToY(100f) + .FadeOut() + .Delay(delay) + .MoveToY(0f, 240, Easing.OutCubic) + .FadeIn(240, Easing.OutCubic); + } + + public void DisappearToRightAndExpire(double delay) + { + Content.Delay(delay) + .FadeOut(240, Easing.InOutCubic) + .MoveToX(300f, 360, Easing.InOutCubic); + + this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); + } + + public void DisappearToBottomAndExpire(double delay) + { + Content.Delay(delay) + .FadeOut(240, Easing.InOutCubic) + .MoveToY(100f, 240, Easing.InOutCubic); + + this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); + } } } diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index 4df4116de1..841f0297e8 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.SelectV2.Footer Icon = FontAwesome.Solid.ExchangeAlt; AccentColour = colours.Lime1; - TopLevelContent.AddRange(new[] + AddRange(new[] { unrankedBadge = new UnrankedBadge(), modDisplayBar = new Container