diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs new file mode 100644 index 0000000000..cd84f8b380 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDifficultyMultiplierDisplay.cs @@ -0,0 +1,40 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneDifficultyMultiplierDisplay : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestDifficultyMultiplierDisplay() + { + DifficultyMultiplierDisplay multiplierDisplay = null; + + AddStep("create content", () => Child = multiplierDisplay = new DifficultyMultiplierDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + AddStep("set multiplier below 1", () => multiplierDisplay.Current.Value = 0.5); + AddStep("set multiplier to 1", () => multiplierDisplay.Current.Value = 1); + AddStep("set multiplier above 1", () => multiplierDisplay.Current.Value = 1.5); + + AddSliderStep("set multiplier", 0, 2, 1d, multiplier => + { + if (multiplierDisplay != null) + multiplierDisplay.Current.Value = multiplier; + }); + } + } +} diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs new file mode 100644 index 0000000000..4fc3a904fa --- /dev/null +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -0,0 +1,185 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class DifficultyMultiplierDisplay : CompositeDrawable, IHasCurrentValue + { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(1) + { + Precision = 0.01 + }; + + private readonly Box underlayBackground; + private readonly Box contentBackground; + private readonly FillFlowContainer multiplierFlow; + private readonly MultiplierCounter multiplierCounter; + + [Resolved] + private OsuColour colours { get; set; } + + [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; + AutoSizeAxes = Axes.X; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + Shear = new Vector2(ModPanel.SHEAR_X, 0), + Children = new Drawable[] + { + underlayBackground = new Box + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS + }, + new GridContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, multiplier_value_area_width) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + CornerRadius = ModPanel.CORNER_RADIUS, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 18 }, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Text = "Difficulty Multiplier", + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } + } + }, + multiplierFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ModPanel.SHEAR_X, 0), + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2, 0), + Children = new Drawable[] + { + multiplierCounter = new MultiplierCounter + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = { BindTarget = Current } + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Times, + Size = new Vector2(7), + Margin = new MarginPadding { Top = 1 } + } + } + } + } + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + contentBackground.Colour = colourProvider.Background4; + } + + 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); + } + + private void updateState() + { + if (Current.IsDefault) + { + underlayBackground.FadeColour(colourProvider.Background3, transition_duration, Easing.OutQuint); + multiplierFlow.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + } + else + { + var backgroundColour = Current.Value < 1 + ? colours.ForModType(ModType.DifficultyReduction) + : colours.ForModType(ModType.DifficultyIncrease); + + underlayBackground.FadeColour(backgroundColour, transition_duration, Easing.OutQuint); + multiplierFlow.FadeColour(colourProvider.Background5, transition_duration, Easing.OutQuint); + } + } + + private class MultiplierCounter : RollingCounter + { + protected override double RollingDuration => 500; + + protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N2"); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + }; + } + } +}