diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 07d014b416..889e6326f7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -12,17 +15,76 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Legacy { - public partial class LegacyManiaComboCounter : LegacyComboCounter + public partial class LegacyManiaComboCounter : CompositeDrawable, ISerialisableDrawable { - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - DisplayedCountText.Anchor = Anchor.Centre; - DisplayedCountText.Origin = Anchor.Centre; + public bool UsesFixedAnchor { get; set; } - PopOutCountText.Anchor = Anchor.Centre; - PopOutCountText.Origin = Anchor.Centre; - PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; + public Bindable Current { get; } = new BindableInt { MinValue = 0 }; + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + private set + { + if (displayedCount.Equals(value)) + return; + + displayedCountText.FadeTo(value == 0 ? 0 : 1); + displayedCountText.Text = value.ToString(CultureInfo.InvariantCulture); + counterContainer.Size = displayedCountText.Size; + + displayedCount = value; + } + } + + private int displayedCount; + + private int previousValue; + + private const double fade_out_duration = 100; + private const double rolling_duration = 20; + + private Container counterContainer = null!; + private LegacySpriteText popOutCountText = null!; + private LegacySpriteText displayedCountText = null!; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, ScoreProcessor scoreProcessor) + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new[] + { + counterContainer = new Container + { + AlwaysPresent = true, + Children = new[] + { + popOutCountText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + Blending = BlendingParameters.Additive, + BypassAutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red, + }, + displayedCountText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + AlwaysPresent = true, + BypassAutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + } + }; + + Current.BindTo(scoreProcessor.Combo); } [Resolved] @@ -34,6 +96,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { base.LoadComplete(); + displayedCountText.Text = popOutCountText.Text = Current.Value.ToString(CultureInfo.InvariantCulture); + + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); + + counterContainer.Size = displayedCountText.Size; + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); @@ -56,36 +124,71 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1); } - protected override void OnCountIncrement() + private void updateCount(bool rolling) { - base.OnCountIncrement(); + int prev = previousValue; + previousValue = Current.Value; - PopOutCountText.Hide(); - DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f)) + if (!IsLoaded) + return; + + if (!rolling) + { + FinishTransforms(false, nameof(DisplayedCount)); + + if (prev + 1 == Current.Value) + onCountIncrement(); + else + onCountChange(); + } + else + onCountRolling(); + } + + private void onCountIncrement() + { + popOutCountText.Hide(); + + DisplayedCount = Current.Value; + displayedCountText.ScaleTo(new Vector2(1f, 1.4f)) .ScaleTo(new Vector2(1f), 300, Easing.Out) .FadeIn(120); } - protected override void OnCountChange() + private void onCountChange() { - base.OnCountChange(); + popOutCountText.Hide(); - PopOutCountText.Hide(); - DisplayedCountText.ScaleTo(1f); + if (Current.Value == 0) + displayedCountText.FadeOut(); + + DisplayedCount = Current.Value; + + displayedCountText.ScaleTo(1f); } - protected override void OnCountRolling() + private void onCountRolling() { if (DisplayedCount > 0) { - PopOutCountText.Text = FormatCount(DisplayedCount); - PopOutCountText.FadeTo(0.8f).FadeOut(200) + popOutCountText.Text = DisplayedCount.ToString(CultureInfo.InvariantCulture); + popOutCountText.FadeTo(0.8f).FadeOut(200) .ScaleTo(1f).ScaleTo(4f, 200); - DisplayedCountText.FadeTo(0.5f, 300); + displayedCountText.FadeTo(0.5f, 300); } - base.OnCountRolling(); + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (DisplayedCount == 0 && Current.Value == 0) + displayedCountText.FadeOut(fade_out_duration); + + this.TransformTo(nameof(DisplayedCount), Current.Value, getProportionalDuration(DisplayedCount, Current.Value)); + } + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3a7bc05300..91188f5bac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -440,8 +440,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo); AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); - AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); - AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyDefaultComboCounter { @@ -454,8 +454,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); - AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); - AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); } private Skin importSkinFromArchives(string filename) diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs deleted file mode 100644 index 7003e0d3c8..0000000000 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Skinning -{ - /// - /// Uses the 'x' symbol and has a pop-out effect while rolling over. - /// - public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable - { - public Bindable Current { get; } = new BindableInt { MinValue = 0 }; - - private const double fade_out_duration = 100; - - /// - /// Duration in milliseconds for the counter roll-up animation for each element. - /// - private const double rolling_duration = 20; - - protected readonly LegacySpriteText PopOutCountText; - protected readonly LegacySpriteText DisplayedCountText; - - private int previousValue; - - private int displayedCount; - - private bool isRolling; - - private readonly Container counterContainer; - - public bool UsesFixedAnchor { get; set; } - - protected LegacyComboCounter() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new[] - { - counterContainer = new Container - { - AlwaysPresent = true, - Children = new[] - { - PopOutCountText = new LegacySpriteText(LegacyFont.Combo) - { - Alpha = 0, - Blending = BlendingParameters.Additive, - BypassAutoSizeAxes = Axes.Both, - }, - DisplayedCountText = new LegacySpriteText(LegacyFont.Combo) - { - Alpha = 0, - AlwaysPresent = true, - BypassAutoSizeAxes = Axes.Both, - }, - } - } - }; - } - - /// - /// Value shown at the current moment. - /// - public virtual int DisplayedCount - { - get => displayedCount; - private set - { - if (displayedCount.Equals(value)) - return; - - if (isRolling) - onDisplayedCountRolling(value); - else if (displayedCount + 1 == value) - onDisplayedCountIncrement(value); - else - onDisplayedCountChange(value); - - displayedCount = value; - } - } - - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) - { - Current.BindTo(scoreProcessor.Combo); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - DisplayedCountText.Text = FormatCount(Current.Value); - PopOutCountText.Text = FormatCount(Current.Value); - - Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void updateCount(bool rolling) - { - int prev = previousValue; - previousValue = Current.Value; - - if (!IsLoaded) - return; - - if (!rolling) - { - FinishTransforms(false, nameof(DisplayedCount)); - - isRolling = false; - DisplayedCount = prev; - - if (prev + 1 == Current.Value) - OnCountIncrement(); - else - OnCountChange(); - } - else - { - OnCountRolling(); - isRolling = true; - } - } - - /// - /// Raised when the counter should display the new value with transitions. - /// - protected virtual void OnCountIncrement() - { - if (DisplayedCount < Current.Value - 1) - DisplayedCount++; - - DisplayedCount++; - } - - /// - /// Raised when the counter should roll to the new combo value (usually roll back to zero). - /// - protected virtual void OnCountRolling() - { - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (DisplayedCount == 0 && Current.Value == 0) - DisplayedCountText.FadeOut(fade_out_duration); - - transformRoll(DisplayedCount, Current.Value); - } - - /// - /// Raised when the counter should display the new combo value without any transitions. - /// - protected virtual void OnCountChange() - { - if (Current.Value == 0) - DisplayedCountText.FadeOut(); - - DisplayedCount = Current.Value; - } - - private void onDisplayedCountRolling(int newValue) - { - if (newValue == 0) - DisplayedCountText.FadeOut(fade_out_duration); - - DisplayedCountText.Text = FormatCount(newValue); - counterContainer.Size = DisplayedCountText.Size; - } - - private void onDisplayedCountChange(int newValue) - { - DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1); - DisplayedCountText.Text = FormatCount(newValue); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void onDisplayedCountIncrement(int newValue) - { - DisplayedCountText.Text = FormatCount(newValue); - - counterContainer.Size = DisplayedCountText.Size; - } - - private void transformRoll(int currentValue, int newValue) => - this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); - - protected virtual string FormatCount(int count) => $@"{count}"; - - private double getProportionalDuration(int currentValue, int newValue) - { - double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; - return difference * rolling_duration; - } - } -} diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs index 6c81b1f959..7de4aee656 100644 --- a/osu.Game/Skinning/LegacyDefaultComboCounter.cs +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -1,8 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Threading; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Skinning @@ -10,73 +14,265 @@ namespace osu.Game.Skinning /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public partial class LegacyDefaultComboCounter : LegacyComboCounter + public partial class LegacyDefaultComboCounter : CompositeDrawable, ISerialisableDrawable { + public Bindable Current { get; } = new BindableInt { MinValue = 0 }; + + private uint scheduledPopOutCurrentId; + private const double big_pop_out_duration = 300; + private const double small_pop_out_duration = 100; - private ScheduledDelegate? scheduledPopOut; + private const double fade_out_duration = 100; + + /// + /// Duration in milliseconds for the counter roll-up animation for each element. + /// + private const double rolling_duration = 20; + + private readonly Drawable popOutCount; + + private readonly Drawable displayedCountSpriteText; + + private int previousValue; + + private int displayedCount; + + private bool isRolling; + + private readonly Container counterContainer; + + public bool UsesFixedAnchor { get; set; } public LegacyDefaultComboCounter() { + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + Margin = new MarginPadding(10); - PopOutCountText.Anchor = Anchor.BottomLeft; - DisplayedCountText.Anchor = Anchor.BottomLeft; + Scale = new Vector2(1.28f); + + InternalChildren = new[] + { + counterContainer = new Container + { + AlwaysPresent = true, + Children = new[] + { + popOutCount = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + Blending = BlendingParameters.Additive, + Anchor = Anchor.BottomLeft, + BypassAutoSizeAxes = Axes.Both, + }, + displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) + { + Alpha = 0, + AlwaysPresent = true, + Anchor = Anchor.BottomLeft, + BypassAutoSizeAxes = Axes.Both, + }, + } + } + }; + } + + /// + /// Value shown at the current moment. + /// + public virtual int DisplayedCount + { + get => displayedCount; + private set + { + if (displayedCount.Equals(value)) + return; + + if (isRolling) + onDisplayedCountRolling(value); + else if (displayedCount + 1 == value) + onDisplayedCountIncrement(value); + else + onDisplayedCountChange(value); + + displayedCount = value; + } + } + + [BackgroundDependencyLoader] + private void load(ScoreProcessor scoreProcessor) + { + Current.BindTo(scoreProcessor.Combo); } protected override void LoadComplete() { base.LoadComplete(); + ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); + ((IHasText)popOutCount).Text = formatCount(Current.Value); + + Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); + + updateLayout(); + } + + private void updateLayout() + { const float font_height_ratio = 0.625f; const float vertical_offset = 9; - DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset); - DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset); + displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset); + displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset); - PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left - PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset); + popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left + popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset); + + counterContainer.Size = displayedCountSpriteText.Size; } - protected override void OnCountIncrement() + private void updateCount(bool rolling) { - DisplayedCountText.Show(); + int prev = previousValue; + previousValue = Current.Value; - PopOutCountText.Text = FormatCount(Current.Value); + if (!IsLoaded) + return; - PopOutCountText.ScaleTo(1.56f) - .ScaleTo(1, big_pop_out_duration); - - PopOutCountText.FadeTo(0.6f) - .FadeOut(big_pop_out_duration); - - this.Delay(big_pop_out_duration - 140).Schedule(() => + if (!rolling) { - base.OnCountIncrement(); + FinishTransforms(false, nameof(DisplayedCount)); + isRolling = false; + DisplayedCount = prev; - DisplayedCountText.ScaleTo(1).Then() - .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() - .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); - }, out scheduledPopOut); + if (prev + 1 == Current.Value) + onCountIncrement(prev, Current.Value); + else + onCountChange(Current.Value); + } + else + { + onCountRolling(displayedCount, Current.Value); + isRolling = true; + } } - protected override void OnCountRolling() + private void transformPopOut(int newValue) { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; + ((IHasText)popOutCount).Text = formatCount(newValue); - base.OnCountRolling(); + popOutCount.ScaleTo(1.56f) + .ScaleTo(1, big_pop_out_duration); + + popOutCount.FadeTo(0.6f) + .FadeOut(big_pop_out_duration); } - protected override void OnCountChange() + private void transformNoPopOut(int newValue) { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); - base.OnCountChange(); + counterContainer.Size = displayedCountSpriteText.Size; + + displayedCountSpriteText.ScaleTo(1); } - protected override string FormatCount(int count) => $@"{count}x"; + private void transformPopOutSmall(int newValue) + { + ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); + + counterContainer.Size = displayedCountSpriteText.Size; + + displayedCountSpriteText.ScaleTo(1).Then() + .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() + .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); + } + + private void scheduledPopOutSmall(uint id) + { + // Too late; scheduled task invalidated + if (id != scheduledPopOutCurrentId) + return; + + DisplayedCount++; + } + + private void onCountIncrement(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + if (DisplayedCount < currentValue) + DisplayedCount++; + + displayedCountSpriteText.Show(); + + transformPopOut(newValue); + + uint newTaskId = scheduledPopOutCurrentId; + + Scheduler.AddDelayed(delegate + { + scheduledPopOutSmall(newTaskId); + }, big_pop_out_duration - 140); + } + + private void onCountRolling(int currentValue, int newValue) + { + scheduledPopOutCurrentId++; + + // Hides displayed count if was increasing from 0 to 1 but didn't finish + if (currentValue == 0 && newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + + transformRoll(currentValue, newValue); + } + + private void onCountChange(int newValue) + { + scheduledPopOutCurrentId++; + + if (newValue == 0) + displayedCountSpriteText.FadeOut(); + + DisplayedCount = newValue; + } + + private void onDisplayedCountRolling(int newValue) + { + if (newValue == 0) + displayedCountSpriteText.FadeOut(fade_out_duration); + else + displayedCountSpriteText.Show(); + + transformNoPopOut(newValue); + } + + private void onDisplayedCountChange(int newValue) + { + displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); + transformNoPopOut(newValue); + } + + private void onDisplayedCountIncrement(int newValue) + { + displayedCountSpriteText.Show(); + transformPopOutSmall(newValue); + } + + private void transformRoll(int currentValue, int newValue) => + this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); + + private string formatCount(int count) => $@"{count}x"; + + private double getProportionalDuration(int currentValue, int newValue) + { + double difference = currentValue > newValue ? currentValue - newValue : newValue - currentValue; + return difference * rolling_duration; + } } }