1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-03 03:20:16 +08:00

Move "attribute adjusted" tooltips to individual attribute displays

Pre-requisite for displaying additional information regarding the impact
of particular attributes on various beatmap metrics.
This commit is contained in:
Bartłomiej Dach
2025-07-31 12:40:48 +02:00
Unverified
parent 3875bd0cf6
commit cb73717a34
6 changed files with 121 additions and 161 deletions
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -16,19 +14,19 @@ using osuTK;
namespace osu.Game.Overlays.Mods
{
public partial class AdjustedAttributesTooltip : VisibilityContainer, ITooltip<AdjustedAttributesTooltip.Data?>
public partial class AdjustedAttributeTooltip : VisibilityContainer, ITooltip<RulesetBeatmapAttribute?>
{
private readonly OverlayColourProvider? colourProvider;
private FillFlowContainer attributesFillFlow = null!;
private Container content = null!;
private Data? data;
private RulesetBeatmapAttribute? attribute;
private OsuSpriteText adjustedByModsText = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
public AdjustedAttributesTooltip(OverlayColourProvider? colourProvider = null)
public AdjustedAttributeTooltip(OverlayColourProvider? colourProvider = null)
{
this.colourProvider = colourProvider;
}
@@ -60,17 +58,12 @@ namespace osu.Game.Overlays.Mods
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
adjustedByModsText = new OsuSpriteText
{
Text = "One or more values are being adjusted by mods.",
Font = OsuFont.Default.With(weight: FontWeight.Bold),
},
attributesFillFlow = new FillFlowContainer
{
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both
}
}
}
},
}
},
};
@@ -80,29 +73,21 @@ namespace osu.Game.Overlays.Mods
private void updateDisplay()
{
attributesFillFlow.Clear();
if (data != null)
if (attribute != null && !Precision.AlmostEquals(attribute.OriginalValue, attribute.AdjustedValue))
{
foreach (var attribute in data.Attributes)
{
if (!Precision.AlmostEquals(attribute.OriginalValue, attribute.AdjustedValue))
attributesFillFlow.Add(new AttributeDisplay(attribute.Acronym, attribute.OriginalValue, attribute.AdjustedValue));
}
}
if (attributesFillFlow.Any())
adjustedByModsText.Text = $"This value is being adjusted by mods ({attribute.OriginalValue:0.0#} → {attribute.AdjustedValue:0.0#}).";
content.Show();
}
else
content.Hide();
}
public void SetContent(Data? data)
public void SetContent(RulesetBeatmapAttribute? attribute)
{
if (this.data == data)
if (this.attribute == attribute)
return;
this.data = data;
this.attribute = attribute;
updateDisplay();
}
@@ -110,29 +95,5 @@ namespace osu.Game.Overlays.Mods
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
public void Move(Vector2 pos) => Position = pos;
public class Data
{
public IReadOnlyCollection<RulesetBeatmapAttribute> Attributes { get; }
public Data(IReadOnlyCollection<RulesetBeatmapAttribute> attributes)
{
Attributes = attributes;
}
}
private partial class AttributeDisplay : CompositeDrawable
{
public AttributeDisplay(string name, double original, double adjusted)
{
AutoSizeAxes = Axes.Both;
InternalChild = new OsuSpriteText
{
Font = OsuFont.Default.With(weight: FontWeight.Bold),
Text = $"{name}: {original:0.0#} → {adjusted:0.0#}"
};
}
}
}
}
@@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
@@ -27,7 +26,7 @@ namespace osu.Game.Overlays.Mods
/// On the mod select overlay, this provides a local updating view of BPM, star rating and other
/// difficulty attributes so the user can have a better insight into what mods are changing.
/// </summary>
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay, IHasCustomTooltip<AdjustedAttributesTooltip.Data?>
public partial class BeatmapAttributesDisplay : ModFooterInformationDisplay
{
private StarRatingDisplay starRatingDisplay = null!;
private BPMDisplay bpmDisplay = null!;
@@ -51,10 +50,6 @@ namespace osu.Game.Overlays.Mods
private CancellationTokenSource? cancellationSource;
private IBindable<StarDifficulty> starDifficulty = null!;
public ITooltip<AdjustedAttributesTooltip.Data?> GetCustomTooltip() => new AdjustedAttributesTooltip();
public AdjustedAttributesTooltip.Data? TooltipContent { get; private set; }
private const float transition_duration = 250;
[BackgroundDependencyLoader]
@@ -164,8 +159,6 @@ namespace osu.Game.Overlays.Mods
Ruleset ruleset = GameRuleset.Value.CreateInstance();
var displayAttributes = ruleset.GetBeatmapAttributesForDisplay(BeatmapInfo.Value, Mods.Value).ToList();
TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes);
// if there are not enough attribute displays, make more
for (int i = RightContent.Count; i < displayAttributes.Count; i++)
RightContent.Add(new VerticalAttributeDisplay { Shear = -OsuGame.SHEAR });
@@ -175,16 +168,12 @@ namespace osu.Game.Overlays.Mods
{
var attribute = displayAttributes[i];
var display = (VerticalAttributeDisplay)RightContent[i];
display.Label = attribute.Acronym;
display.Current.Value = attribute.AdjustedValue;
display.AdjustType.Value = VerticalAttributeDisplay.CalculateEffect(attribute.OriginalValue, attribute.AdjustedValue);
display.Alpha = 1;
display.SetAttribute(attribute);
}
// and hide any extra ones
for (int i = displayAttributes.Count; i < RightContent.Count; i++)
RightContent[i].Alpha = 0;
((VerticalAttributeDisplay)RightContent[i]).SetAttribute(null);
});
private void updateCollapsedState()
@@ -7,29 +7,22 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osuTK.Graphics;
namespace osu.Game.Overlays.Mods
{
public partial class VerticalAttributeDisplay : Container, IHasCurrentValue<double>
public partial class VerticalAttributeDisplay : Container, IHasCustomTooltip<RulesetBeatmapAttribute?>
{
public Bindable<double> Current
{
get => current.Current;
set => current.Current = value;
}
private readonly BindableWithCurrent<double> current = new BindableWithCurrent<double>();
public Bindable<ModEffect> AdjustType = new Bindable<ModEffect>();
/// <summary>
/// Text to display in the top area of the display.
/// </summary>
@@ -45,11 +38,70 @@ namespace osu.Game.Overlays.Mods
[Resolved]
private OsuColour colours { get; set; } = null!;
private void updateTextColor()
public VerticalAttributeDisplay()
{
AutoSizeAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
Origin = Anchor.CentreLeft;
Anchor = Anchor.CentreLeft;
InternalChild = new FillFlowContainer
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y,
Width = 42,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold)
},
counter = new EffectCounter
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Current = { BindTarget = current },
}
}
};
}
public void SetAttribute(RulesetBeatmapAttribute? attribute)
{
if (attribute != null)
{
text.Text = attribute.Acronym;
current.Value = attribute.AdjustedValue;
var effect = calculateEffect(attribute.OriginalValue, attribute.AdjustedValue);
updateTextColor(effect);
Alpha = 1;
}
else
Alpha = 0;
TooltipContent = attribute;
}
private static ModEffect calculateEffect(double oldValue, double newValue)
{
if (Precision.AlmostEquals(newValue, oldValue, 0.01))
return ModEffect.NotChanged;
if (newValue < oldValue)
return ModEffect.DifficultyReduction;
return ModEffect.DifficultyIncrease;
}
private void updateTextColor(ModEffect effect)
{
Color4 newColor;
switch (AdjustType.Value)
switch (effect)
{
case ModEffect.NotChanged:
newColor = Color4.White;
@@ -64,58 +116,13 @@ namespace osu.Game.Overlays.Mods
break;
default:
throw new ArgumentOutOfRangeException(nameof(AdjustType.Value));
throw new ArgumentOutOfRangeException(nameof(effect), effect, null);
}
text.Colour = newColor;
counter.Colour = newColor;
}
public VerticalAttributeDisplay()
{
AutoSizeAxes = Axes.X;
Origin = Anchor.CentreLeft;
Anchor = Anchor.CentreLeft;
AdjustType.BindValueChanged(_ => updateTextColor());
InternalChild = new FillFlowContainer
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
AutoSizeAxes = Axes.Y,
Width = 50,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Margin = new MarginPadding { Horizontal = 15 }, // to reserve space for 0.XX value
Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold)
},
counter = new EffectCounter
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Current = { BindTarget = Current },
}
}
};
}
public static ModEffect CalculateEffect(double oldValue, double newValue)
{
if (Precision.AlmostEquals(newValue, oldValue, 0.01))
return ModEffect.NotChanged;
if (newValue < oldValue)
return ModEffect.DifficultyReduction;
return ModEffect.DifficultyIncrease;
}
public enum ModEffect
{
NotChanged,
@@ -134,5 +141,8 @@ namespace osu.Game.Overlays.Mods
Font = OsuFont.Default.With(size: 18, weight: FontWeight.SemiBold)
};
}
public ITooltip<RulesetBeatmapAttribute?> GetCustomTooltip() => new AdjustedAttributeTooltip();
public RulesetBeatmapAttribute? TooltipContent { get; set; }
}
}
@@ -21,6 +21,7 @@ using System.Linq;
using osu.Game.Rulesets.Mods;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Extensions;
using osu.Framework.Localisation;
using osu.Framework.Threading;
@@ -29,11 +30,12 @@ using osu.Game.Configuration;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Utils;
namespace osu.Game.Screens.Select.Details
{
public partial class AdvancedStats : Container, IHasCustomTooltip<AdjustedAttributesTooltip.Data>
public partial class AdvancedStats : Container
{
private readonly int columns;
@@ -43,9 +45,6 @@ namespace osu.Game.Screens.Select.Details
protected FillFlowContainer Flow { get; private set; }
private readonly StatisticRow starDifficulty;
public ITooltip<AdjustedAttributesTooltip.Data> GetCustomTooltip() => new AdjustedAttributesTooltip();
public AdjustedAttributesTooltip.Data TooltipContent { get; private set; }
private IBeatmapInfo beatmapInfo;
public IBeatmapInfo BeatmapInfo
@@ -160,7 +159,6 @@ namespace osu.Game.Screens.Select.Details
if (BeatmapInfo != null && Ruleset.Value != null)
{
var displayAttributes = Ruleset.Value.CreateInstance().GetBeatmapAttributesForDisplay(BeatmapInfo, Mods.Value).ToList();
TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes);
// if there are not enough attribute displays, make more
// the subtraction of 1 is to exclude the star rating row which is always present (and always last)
@@ -177,17 +175,13 @@ namespace osu.Game.Screens.Select.Details
for (int i = 0; i < displayAttributes.Count; i++)
{
var attribute = displayAttributes[i];
var display = (StatisticRow)Flow.Where(r => r != starDifficulty).ElementAt(i);
display.Title = attribute.Label;
display.MaxValue = attribute.MaxValue;
display.Value = (attribute.OriginalValue, attribute.AdjustedValue);
display.Alpha = 1;
var row = (StatisticRow)Flow.Where(r => r != starDifficulty).ElementAt(i);
row.SetAttribute(attribute);
}
// and hide any extra ones
foreach (var row in Flow.Where(r => r != starDifficulty).Skip(displayAttributes.Count))
row.Alpha = 0;
((StatisticRow)row).SetAttribute(null);
}
updateStarDifficulty();
@@ -233,7 +227,7 @@ namespace osu.Game.Screens.Select.Details
starDifficultyCancellationSource?.Cancel();
}
public partial class StatisticRow : Container, IHasAccentColour
public partial class StatisticRow : Container, IHasAccentColour, IHasCustomTooltip<RulesetBeatmapAttribute>
{
private const float value_width = 25;
private const float name_width = 70;
@@ -352,6 +346,26 @@ namespace osu.Game.Screens.Select.Details
},
};
}
public void SetAttribute([CanBeNull] RulesetBeatmapAttribute attribute)
{
if (attribute != null)
{
Title = attribute.Label;
MaxValue = attribute.MaxValue;
Value = (attribute.OriginalValue, attribute.AdjustedValue);
Alpha = 1;
}
else
Alpha = 0;
TooltipContent = attribute;
}
public ITooltip<RulesetBeatmapAttribute> GetCustomTooltip() => new AdjustedAttributeTooltip();
[CanBeNull]
public RulesetBeatmapAttribute TooltipContent { get; set; }
}
}
}
@@ -11,7 +11,6 @@ using osu.Framework.Bindables;
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.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
@@ -23,7 +22,6 @@ using osu.Game.Localisation;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osuTK.Graphics;
@@ -62,7 +60,7 @@ namespace osu.Game.Screens.SelectV2
private GridContainer ratingAndNameContainer = null!;
private DifficultyStatisticsDisplay countStatisticsDisplay = null!;
private AdjustableDifficultyStatisticsDisplay difficultyStatisticsDisplay = null!;
private DifficultyStatisticsDisplay difficultyStatisticsDisplay = null!;
private CancellationTokenSource? cancellationSource;
@@ -195,7 +193,7 @@ namespace osu.Game.Screens.SelectV2
RelativeSizeAxes = Axes.X,
},
Empty(),
difficultyStatisticsDisplay = new AdjustableDifficultyStatisticsDisplay(autoSize: true),
difficultyStatisticsDisplay = new DifficultyStatisticsDisplay(autoSize: true),
}
},
}
@@ -289,7 +287,6 @@ namespace osu.Game.Screens.SelectV2
{
if (beatmap.IsDefault || ruleset.Value == null)
{
difficultyStatisticsDisplay.TooltipContent = null;
difficultyStatisticsDisplay.Statistics = Array.Empty<StatisticDifficulty.Data>();
return;
}
@@ -297,7 +294,6 @@ namespace osu.Game.Screens.SelectV2
Ruleset rulesetInstance = ruleset.Value.CreateInstance();
var displayAttributes = rulesetInstance.GetBeatmapAttributesForDisplay(beatmap.Value.BeatmapInfo, mods.Value).ToList();
difficultyStatisticsDisplay.TooltipContent = new AdjustedAttributesTooltip.Data(displayAttributes);
difficultyStatisticsDisplay.Statistics = displayAttributes.Select(a => new StatisticDifficulty.Data(a)).ToList();
});
@@ -325,21 +321,6 @@ namespace osu.Game.Screens.SelectV2
IdleColour = overlayColourProvider?.Light2 ?? colours.Blue;
}
}
private partial class AdjustableDifficultyStatisticsDisplay : DifficultyStatisticsDisplay, IHasCustomTooltip<AdjustedAttributesTooltip.Data>
{
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
public ITooltip<AdjustedAttributesTooltip.Data> GetCustomTooltip() => new AdjustedAttributesTooltip(colourProvider);
public AdjustedAttributesTooltip.Data? TooltipContent { get; set; }
public AdjustableDifficultyStatisticsDisplay(bool autoSize)
: base(autoSize)
{
}
}
}
}
}
@@ -7,12 +7,14 @@ using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Difficulty;
using osuTK;
using osuTK.Graphics;
@@ -21,7 +23,7 @@ namespace osu.Game.Screens.SelectV2
{
public partial class BeatmapTitleWedge
{
public partial class StatisticDifficulty : CompositeDrawable, IHasAccentColour
public partial class StatisticDifficulty : CompositeDrawable, IHasAccentColour, IHasCustomTooltip<RulesetBeatmapAttribute?>
{
private Data value = new Data(string.Empty, 0, 0, 0);
@@ -192,13 +194,16 @@ namespace osu.Game.Screens.SelectV2
}
}
public record Data(LocalisableString Label, float Value, float AdjustedValue, float Maximum, string? Content = null)
public record Data(LocalisableString Label, float Value, float AdjustedValue, float Maximum, string? Content = null, RulesetBeatmapAttribute? BeatmapAttribute = null)
{
public Data(RulesetBeatmapAttribute attribute)
: this(attribute.Label, attribute.OriginalValue, attribute.AdjustedValue, attribute.MaxValue)
: this(attribute.Label, attribute.OriginalValue, attribute.AdjustedValue, attribute.MaxValue, BeatmapAttribute: attribute)
{
}
}
public ITooltip<RulesetBeatmapAttribute?> GetCustomTooltip() => new AdjustedAttributeTooltip();
public RulesetBeatmapAttribute? TooltipContent => value.BeatmapAttribute;
}
}
}