mirror of
https://github.com/ppy/osu.git
synced 2026-06-08 22:44:40 +08:00
Merge pull request #32970 from peppy/mod-icon-improvements
Improve visibility of setting adjustments on mod icons
This commit is contained in:
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@@ -36,6 +37,24 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
[SettingSource("Spicy Patterns", "Adjust the patterns as if Hard Rock is enabled.")]
|
||||
public BindableBool HardRockOffsets { get; } = new BindableBool();
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
if (!ApproachRate.IsDefault) return format("AR", ApproachRate);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -36,6 +37,24 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
};
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
if (!ApproachRate.IsDefault) return format("AR", ApproachRate);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
@@ -20,6 +21,23 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
ReadCurrentFromDifficulty = _ => 1,
|
||||
};
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
|
||||
@@ -12,22 +12,81 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneModIcon : OsuTestScene
|
||||
{
|
||||
private FillFlowContainer spreadOutFlow = null!;
|
||||
private ModDisplay modDisplay = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create flows", () =>
|
||||
{
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Relative, 0.5f),
|
||||
new Dimension(GridSizeMode.Relative, 0.5f),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
modDisplay = new ModDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
spreadOutFlow = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Full,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private void addRange(IEnumerable<IMod> mods)
|
||||
{
|
||||
spreadOutFlow.AddRange(mods.Select(m => new ModIcon(m)));
|
||||
modDisplay.Current.Value = modDisplay.Current.Value.Concat(mods.OfType<Mod>()).ToList();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowAllMods()
|
||||
{
|
||||
AddStep("create mod icons", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
addRange(Ruleset.Value.CreateInstance().CreateAllMods().Select(m =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)),
|
||||
};
|
||||
if (m is OsuModFlashlight fl)
|
||||
fl.FollowDelay.Value = 1245;
|
||||
|
||||
if (m is OsuModDaycore dc)
|
||||
dc.SpeedChange.Value = 0.74f;
|
||||
|
||||
if (m is OsuModDifficultyAdjust da)
|
||||
da.CircleSize.Value = 8.2f;
|
||||
|
||||
if (m is ModAdaptiveSpeed ad)
|
||||
ad.AdjustPitch.Value = false;
|
||||
|
||||
return m;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("toggle selected", () =>
|
||||
@@ -42,26 +101,22 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("create mod icons", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
var rateAdjustMods = Ruleset.Value.CreateInstance().CreateAllMods()
|
||||
.OfType<ModRateAdjust>();
|
||||
|
||||
addRange(rateAdjustMods.SelectMany(m =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods()
|
||||
.OfType<ModRateAdjust>()
|
||||
.SelectMany(m =>
|
||||
{
|
||||
List<ModIcon> icons = new List<ModIcon> { new ModIcon(m) };
|
||||
List<Mod> mods = new List<Mod> { 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));
|
||||
}
|
||||
for (double i = m.SpeedChange.MinValue; i < m.SpeedChange.MaxValue; i += m.SpeedChange.Precision * 10)
|
||||
{
|
||||
m = (ModRateAdjust)m.DeepClone();
|
||||
m.SpeedChange.Value = i;
|
||||
mods.Add(m);
|
||||
}
|
||||
|
||||
return icons;
|
||||
}),
|
||||
};
|
||||
return mods;
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("adjust rates", () =>
|
||||
@@ -81,21 +136,50 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
[Test]
|
||||
public void TestChangeModType()
|
||||
{
|
||||
ModIcon icon = null!;
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
|
||||
AddStep("change mod", () => icon.Mod = new OsuModEasy());
|
||||
AddStep("create mod icon", () => addRange([new OsuModDoubleTime()]));
|
||||
AddStep("change mod", () =>
|
||||
{
|
||||
foreach (var modIcon in this.ChildrenOfType<ModIcon>())
|
||||
modIcon.Mod = new OsuModEasy();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInterfaceModType()
|
||||
{
|
||||
ModIcon icon = null!;
|
||||
|
||||
var ruleset = new OsuRuleset();
|
||||
|
||||
AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
|
||||
AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
|
||||
AddStep("create mod icon", () => addRange([ruleset.AllMods.First(m => m.Acronym == "DT")]));
|
||||
AddStep("change mod", () =>
|
||||
{
|
||||
foreach (var modIcon in this.ChildrenOfType<ModIcon>())
|
||||
modIcon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDifficultyAdjust()
|
||||
{
|
||||
AddStep("create icons", () =>
|
||||
{
|
||||
addRange([
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 8 }
|
||||
},
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 5.5f }
|
||||
},
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 8 },
|
||||
ApproachRate = { Value = 8 },
|
||||
OverallDifficulty = { Value = 8 },
|
||||
DrainRate = { Value = 8 },
|
||||
}
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace osu.Game.Extensions
|
||||
/// <param name="maxDecimalDigits">The maximum number of decimals to be considered in the original value.</param>
|
||||
/// <param name="asPercentage">Whether the output should be a percentage. For integer types, 0-100 is mapped to 0-100%; for other types 0-1 is mapped to 0-100%.</param>
|
||||
/// <returns>The formatted output.</returns>
|
||||
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage) where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage = false) where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
double floatValue = double.CreateTruncating(value);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@@ -81,5 +83,33 @@ namespace osu.Game.Rulesets.Mods
|
||||
/// Create a fresh <see cref="Mod"/> instance based on this mod.
|
||||
/// </summary>
|
||||
Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType())!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether any user adjustable setting attached to this mod has a non-default value.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This returns the instantaneous state of this mod. It may change over time.
|
||||
/// For tracking changes on a dynamic display, make sure to setup a <see cref="ModSettingChangeTracker"/>.
|
||||
/// </remarks>
|
||||
bool HasNonDefaultSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
bool hasAdjustments = false;
|
||||
|
||||
foreach (var (_, property) in this.GetSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(this)!;
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
{
|
||||
hasAdjustments = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasAdjustments;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
@@ -67,6 +68,22 @@ namespace osu.Game.Rulesets.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public override string ExtendedIconInformation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
return string.Empty;
|
||||
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<(LocalisableString setting, LocalisableString value)> SettingDescription
|
||||
{
|
||||
get
|
||||
@@ -94,5 +111,26 @@ namespace osu.Game.Rulesets.Mods
|
||||
if (DrainRate.Value != null) difficulty.DrainRate = DrainRate.Value.Value;
|
||||
if (OverallDifficulty.Value != null) difficulty.OverallDifficulty = OverallDifficulty.Value.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of settings on this mod instance which have been adjusted by the user from their default values.
|
||||
/// </summary>
|
||||
protected int UserAdjustedSettingsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var (_, property) in this.GetSettingsSourceProperties())
|
||||
{
|
||||
var bindable = (IBindable)property.GetValue(this)!;
|
||||
|
||||
if (!bindable.IsDefault)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
@@ -81,6 +82,11 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
private Container extendedContent = null!;
|
||||
|
||||
private Drawable adjustmentMarker = null!;
|
||||
|
||||
private Circle cogBackground = null!;
|
||||
private SpriteIcon cog = null!;
|
||||
|
||||
private ModSettingChangeTracker? modSettingsChangeTracker;
|
||||
|
||||
/// <summary>
|
||||
@@ -139,7 +145,7 @@ namespace osu.Game.Rulesets.UI
|
||||
Origin = Anchor.CentreLeft,
|
||||
Name = "main content",
|
||||
Size = MOD_ICON_SIZE,
|
||||
Children = new Drawable[]
|
||||
Children = new[]
|
||||
{
|
||||
background = new Sprite
|
||||
{
|
||||
@@ -165,6 +171,29 @@ namespace osu.Game.Rulesets.UI
|
||||
Size = new Vector2(45),
|
||||
Icon = FontAwesome.Solid.Question
|
||||
},
|
||||
adjustmentMarker = new Container
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
Origin = Anchor.Centre,
|
||||
Position = new Vector2(64, 14),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
cogBackground = new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
cog = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Cog,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.6f),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -216,11 +245,18 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
extendedContent.Alpha = showExtended ? 1 : 0;
|
||||
extendedText.Text = mod.ExtendedIconInformation;
|
||||
|
||||
if (mod.HasNonDefaultSettings)
|
||||
adjustmentMarker.Show();
|
||||
else
|
||||
adjustmentMarker.Hide();
|
||||
}
|
||||
|
||||
private void updateColour()
|
||||
{
|
||||
modAcronym.Colour = modIcon.Colour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, backgroundColour, 0, 1);
|
||||
cogBackground.Colour = Interpolation.ValueAt<Colour4>(0.1f, Colour4.Black, backgroundColour, 0, 1);
|
||||
cog.Colour = backgroundColour;
|
||||
|
||||
extendedText.Colour = background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour;
|
||||
extendedBackground.Colour = Selected.Value ? backgroundColour.Darken(2.4f) : backgroundColour.Darken(2.8f);
|
||||
|
||||
Reference in New Issue
Block a user