1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 12:42:54 +08:00
osu-lazer/osu.Game/Overlays/Profile/UserGraph.cs
Salman Ahmed a664efe12b Fix history graph tooltips leaking to others
Since there was no check about which tooltip content came from which graph, all history graphs use the "Replays Watched" tooltip, as it is the latest created one.
2021-04-13 07:59:14 +03:00

296 lines
10 KiB
C#

// 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;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Overlays.Profile
{
/// <summary>
/// Graph which is used in <see cref="UserProfileOverlay"/> to present changes in user statistics over time.
/// </summary>
/// <typeparam name="TKey">Type of data to be used for X-axis of the graph.</typeparam>
/// <typeparam name="TValue">Type of data to be used for Y-axis of the graph.</typeparam>
public abstract class UserGraph<TKey, TValue> : Container, IHasCustomTooltip
{
protected const float FADE_DURATION = 150;
private readonly UserLineGraph graph;
private KeyValuePair<TKey, TValue>[] data;
private int hoveredIndex = -1;
protected UserGraph()
{
Add(graph = new UserLineGraph
{
RelativeSizeAxes = Axes.Both,
Alpha = 0
});
graph.OnBallMove += i => hoveredIndex = i;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
graph.LineColour = colours.Yellow;
}
private float lastHoverPosition;
protected override bool OnHover(HoverEvent e)
{
if (data?.Length > 1)
{
graph.UpdateBallPosition(lastHoverPosition = e.MousePosition.X);
graph.ShowBar();
return true;
}
return base.OnHover(e);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (data?.Length > 1)
graph.UpdateBallPosition(e.MousePosition.X);
return base.OnMouseMove(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
graph.HideBar();
base.OnHoverLost(e);
}
/// <summary>
/// Set of values which will be used to create a graph.
/// </summary>
[CanBeNull]
protected KeyValuePair<TKey, TValue>[] Data
{
set
{
data = value;
redrawGraph();
}
}
private void redrawGraph()
{
hoveredIndex = -1;
if (data?.Length > 1)
{
graph.DefaultValueCount = data.Length;
graph.Values = data.Select(pair => GetDataPointHeight(pair.Value)).ToArray();
ShowGraph();
if (IsHovered)
graph.UpdateBallPosition(lastHoverPosition);
return;
}
HideGraph();
}
/// <summary>
/// Function used to convert <see cref="Data"/> point to it's Y-axis position on the graph.
/// </summary>
/// <param name="value">Value to convert.</param>
protected abstract float GetDataPointHeight(TValue value);
protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out);
protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out);
public ITooltip GetCustomTooltip() => GetTooltip();
protected abstract UserGraphTooltip GetTooltip();
public object TooltipContent
{
get
{
if (data == null || hoveredIndex == -1)
return null;
var (key, value) = data[hoveredIndex];
return GetTooltipContent(key, value);
}
}
protected abstract object GetTooltipContent(TKey key, TValue value);
protected class UserLineGraph : LineGraph
{
private readonly CircularContainer movingBall;
private readonly Container bar;
private readonly Box ballBg;
private readonly Box line;
public Action<int> OnBallMove;
public UserLineGraph()
{
Add(bar = new Container
{
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Alpha = 0,
RelativePositionAxes = Axes.Both,
Children = new Drawable[]
{
line = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 2,
},
movingBall = new CircularContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Size = new Vector2(20),
Masking = true,
BorderThickness = 4,
RelativePositionAxes = Axes.Y,
Child = ballBg = new Box { RelativeSizeAxes = Axes.Both }
}
}
});
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
ballBg.Colour = colourProvider.Background5;
movingBall.BorderColour = line.Colour = colours.Yellow;
}
public void UpdateBallPosition(float mouseXPosition)
{
const int duration = 200;
int index = calculateIndex(mouseXPosition);
Vector2 position = calculateBallPosition(index);
movingBall.MoveToY(position.Y, duration, Easing.OutQuint);
bar.MoveToX(position.X, duration, Easing.OutQuint);
OnBallMove.Invoke(index);
}
public void ShowBar() => bar.FadeIn(FADE_DURATION);
public void HideBar() => bar.FadeOut(FADE_DURATION);
private int calculateIndex(float mouseXPosition) => (int)Math.Clamp(MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)), 0, DefaultValueCount - 1);
private Vector2 calculateBallPosition(int index)
{
float y = GetYPosition(Values.ElementAt(index));
return new Vector2(index / (float)(DefaultValueCount - 1), y);
}
}
protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip
{
protected new readonly OsuSpriteText Name;
protected readonly OsuSpriteText Counter, BottomText;
private readonly Box background;
protected UserGraphTooltip(string tooltipCounterName)
{
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 10;
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(10),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
Name = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Text = tooltipCounterName
},
Counter = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
}
}
},
BottomText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
// Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231)
// If above is fixed, this should use OverlayColourProvider
background.Colour = colours.Gray1;
}
public abstract bool SetContent(object content);
private bool instantMove = true;
public void Move(Vector2 pos)
{
if (instantMove)
{
Position = pos;
instantMove = false;
}
else
this.MoveTo(pos, 200, Easing.OutQuint);
}
protected override void PopIn()
{
instantMove |= !IsPresent;
this.FadeIn(200, Easing.OutQuint);
}
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
}
}
}