diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index 897d5fd9f5..15b3e5f310 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -25,6 +29,54 @@ namespace osu.Game.Tests.Visual.UserInterface ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)), }; }); + + AddStep("toggle selected", () => + { + foreach (var icon in this.ChildrenOfType()) + icon.Selected.Toggle(); + }); + } + + [Test] + public void TestShowRateAdjusts() + { + AddStep("create mod icons", () => + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, + ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods() + .OfType() + .SelectMany((m) => + { + List icons = new List { new ModIcon(m) }; + + for (double i = m.SpeedChange.MinValue; i < m.SpeedChange.MaxValue; i += m.SpeedChange.Precision * 10) + { + m = (ModRateAdjust)m.DeepClone(); + m.SpeedChange.Value = i; + icons.Add(new ModIcon(m)); + } + + return icons; + }), + }; + }); + + AddStep("adjust rates", () => + { + foreach (var icon in this.ChildrenOfType()) + { + if (icon.Mod is ModRateAdjust rateAdjust) + { + if (RNG.NextDouble() > 0.9) + rateAdjust.SpeedChange.Value = rateAdjust.SpeedChange.Default; + else + rateAdjust.SpeedChange.Value = RNG.NextDouble(rateAdjust.SpeedChange.MinValue, rateAdjust.SpeedChange.MaxValue); + } + } + }); } [Test] diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 05b2510e53..06c470ff2a 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -19,6 +19,12 @@ namespace osu.Game.Rulesets.Mods /// string Name { get; } + /// + /// Short import information to display on the mod icon. For example, a rate adjust mod's rate + /// or similarly important setting. + /// + string ExtendedIconInformation { get; } + /// /// The user readable description of this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a9ecf89df1..a0bdc9ff51 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -27,6 +27,9 @@ namespace osu.Game.Rulesets.Mods public abstract string Acronym { get; } + [JsonIgnore] + public virtual string ExtendedIconInformation => string.Empty; + [JsonIgnore] public virtual IconUsage? Icon => null; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 7b55ba4ad0..5dad01e015 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -28,5 +28,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp), typeof(ModAdaptiveSpeed), typeof(ModRateAdjust) }; public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; + + public override string ExtendedIconInformation => SettingDescription; } } diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index bf212ad72f..58f7f2c90d 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI @@ -27,9 +28,9 @@ namespace osu.Game.Rulesets.UI { public readonly BindableBool Selected = new BindableBool(); - private readonly SpriteIcon modIcon; - private readonly SpriteText modAcronym; - private readonly SpriteIcon background; + private SpriteIcon modIcon; + private SpriteText modAcronym; + private SpriteIcon background; private const float size = 80; @@ -55,6 +56,10 @@ namespace osu.Game.Rulesets.UI private Color4 backgroundColour; + private Sprite extendedBackground; + + private OsuSpriteText extendedText; + /// /// Construct a new instance. /// @@ -64,8 +69,12 @@ namespace osu.Game.Rulesets.UI { this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); this.showTooltip = showTooltip; + } - Size = new Vector2(size); + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { @@ -77,6 +86,23 @@ namespace osu.Game.Rulesets.UI Icon = OsuIcon.ModBg, Shadow = true, }, + extendedBackground = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get("Icons/BeatmapDetails/mod-icon-extender"), + X = size - 3, + Size = new Vector2(120, 55), + }, + extendedText = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = OsuFont.Default.With(size: 30f, weight: FontWeight.Bold), + UseFullGlyphHeight = false, + X = size, + Text = mod.ExtendedIconInformation, + }, modAcronym = new OsuSpriteText { Origin = Anchor.Centre, @@ -129,7 +155,8 @@ namespace osu.Game.Rulesets.UI private void updateColour() { - background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; + extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; + extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f); } } }