mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 23:10:51 +08:00
c84777d7bf
the x axis division was set to a fixed number of steps rather than picking a neat step size, which created uneven numbers. this pr changes this so an appropriate factor is determined, the x-axis min/max is floor'd/ceil'd to that factor and the divisions are created based on that factor. the cumulative rating line also extends to the new end of the graph. in addition, the bars of the bar chart are now aligned using the left edge of the bar rather than the center. in the after-image, note how the left edge of the bar for 1600 rating aligns with the division. (this is based on the assumption that a rating bucket, say "1500", spans the interval [1500, 1600]. if the bucket spans [1450, 1550] instead, i will revert the change) before <img width="632" height="217" alt="image" src="https://github.com/user-attachments/assets/b7053d43-99bb-4e5b-87a4-dcec37d56b50" /> after <img width="608" height="230" alt="image" src="https://github.com/user-attachments/assets/3f9e4284-f1b1-4bcb-8f70-f75f4e242b19" /> could optimize the while loop into a single mathematical expression, but this is easier to read imo. lmk if you'd prefer the expression instead
766 lines
30 KiB
C#
766 lines
30 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.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Graphics.Cursor;
|
|
using osu.Framework.Graphics.Lines;
|
|
using osu.Framework.Graphics.Shapes;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Framework.Layout;
|
|
using osu.Framework.Utils;
|
|
using osu.Game.Graphics;
|
|
using osu.Game.Graphics.Containers;
|
|
using osu.Game.Graphics.Sprites;
|
|
using osu.Game.Overlays;
|
|
using osuTK;
|
|
using osuTK.Graphics;
|
|
|
|
namespace osu.Game.Screens.OnlinePlay.Matchmaking.Queue
|
|
{
|
|
public partial class RatingDistributionGraph : CompositeDrawable, IHasCustomTooltip<RatingDistributionGraph.RatingDistributionGraphTooltipData>
|
|
{
|
|
private const int y_divisions = 4;
|
|
private const int x_max_divisions = 16;
|
|
|
|
[Resolved]
|
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
|
|
|
[Resolved]
|
|
private OsuColour colours { get; set; } = null!;
|
|
|
|
private Container yAxisLeftContainer = null!;
|
|
private Container yAxisRightContainer = null!;
|
|
private Container xAxisContainer = null!;
|
|
private Container chartContainer = null!;
|
|
|
|
private Container gridContainer = null!;
|
|
private Container barsContainer = null!;
|
|
private Container userRatingContainer = null!;
|
|
private PointPath cumulativePath = null!;
|
|
|
|
private Drawable hoverMarker = null!;
|
|
private Drawable hoverMarkerFill = null!;
|
|
private OsuTextFlowContainer descriptionText = null!;
|
|
|
|
private (int x, int y)[] data = [];
|
|
private int? userRating;
|
|
private (int min, int max, int step) xRange;
|
|
private (int min, int max) yRange;
|
|
private int xDivisionStep = 1;
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
InternalChild = new GridContainer
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Padding = new MarginPadding { Top = 20 },
|
|
RowDimensions =
|
|
[
|
|
// Chart
|
|
new Dimension(),
|
|
// x-axis
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
// "Rating"
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
// Description text
|
|
new Dimension(GridSizeMode.AutoSize)
|
|
],
|
|
Content = new[]
|
|
{
|
|
new Drawable[]
|
|
{
|
|
new GridContainer
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
ColumnDimensions = new[]
|
|
{
|
|
// "Players"
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
// Left y-axis
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
// Chart
|
|
new Dimension(),
|
|
// Right y-axis
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
// "Cumulative"
|
|
new Dimension(GridSizeMode.AutoSize),
|
|
},
|
|
Content = new[]
|
|
{
|
|
new Drawable[]
|
|
{
|
|
new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.CentreLeft,
|
|
Origin = Anchor.TopCentre,
|
|
Text = "Players",
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Rotation = -90,
|
|
Colour = colourProvider.Foreground1
|
|
},
|
|
yAxisLeftContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Y,
|
|
AutoSizeAxes = Axes.X,
|
|
Margin = new MarginPadding { Left = 5, Right = 5 },
|
|
},
|
|
chartContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Children = new[]
|
|
{
|
|
gridContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both
|
|
},
|
|
barsContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Masking = true
|
|
},
|
|
userRatingContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both
|
|
},
|
|
new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
// Margin and padding to better align the paths.
|
|
Margin = new MarginPadding { Left = -2 },
|
|
Padding = new MarginPadding { Right = -2 },
|
|
Children = new Drawable[]
|
|
{
|
|
new PointPath
|
|
{
|
|
AutoSizeAxes = Axes.None,
|
|
RelativeSizeAxes = Axes.Both,
|
|
PathRadius = 2,
|
|
Colour = colourProvider.Colour0,
|
|
},
|
|
cumulativePath = new PointPath
|
|
{
|
|
AutoSizeAxes = Axes.None,
|
|
RelativeSizeAxes = Axes.Both,
|
|
PathRadius = 2,
|
|
Colour = colours.Yellow,
|
|
Offset = new Vector2(0, -3)
|
|
},
|
|
}
|
|
},
|
|
hoverMarker = new CircularContainer
|
|
{
|
|
Origin = Anchor.Centre,
|
|
Size = new Vector2(12),
|
|
Masking = true,
|
|
BorderThickness = 2,
|
|
BorderColour = Color4.White,
|
|
Alpha = 0,
|
|
Child = hoverMarkerFill = new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both
|
|
}
|
|
}
|
|
}
|
|
},
|
|
yAxisRightContainer = new Container
|
|
{
|
|
RelativeSizeAxes = Axes.Y,
|
|
AutoSizeAxes = Axes.X,
|
|
Margin = new MarginPadding { Left = 5 },
|
|
},
|
|
new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.CentreRight,
|
|
Origin = Anchor.TopCentre,
|
|
Text = "Cumulative",
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Rotation = 90,
|
|
Colour = colourProvider.Foreground1
|
|
},
|
|
}
|
|
}
|
|
}
|
|
},
|
|
new Drawable[]
|
|
{
|
|
xAxisContainer = new Container
|
|
{
|
|
AutoSizeAxes = Axes.Y,
|
|
Margin = new MarginPadding { Top = 8 }
|
|
}
|
|
},
|
|
new Drawable[]
|
|
{
|
|
new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.BottomCentre,
|
|
Origin = Anchor.BottomCentre,
|
|
Margin = new MarginPadding { Top = 2 },
|
|
Text = "Rating",
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Colour = colourProvider.Foreground1
|
|
},
|
|
},
|
|
new Drawable[]
|
|
{
|
|
descriptionText = new OsuTextFlowContainer
|
|
{
|
|
Anchor = Anchor.Centre,
|
|
Origin = Anchor.Centre,
|
|
AutoSizeAxes = Axes.X,
|
|
Height = 16,
|
|
Margin = new MarginPadding { Top = 4 }
|
|
},
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
base.LoadComplete();
|
|
updateGraph();
|
|
}
|
|
|
|
public void SetData((int x, int y)[] data, int? userRating)
|
|
{
|
|
this.data = data;
|
|
this.userRating = userRating;
|
|
|
|
xRange = (
|
|
data.Select(d => d.x).DefaultIfEmpty().Min(),
|
|
data.Select(d => d.x).DefaultIfEmpty().Max(),
|
|
data.Zip(data.Skip(1), (a, b) => Math.Abs(b.x - a.x)).DefaultIfEmpty().Min()
|
|
);
|
|
|
|
xDivisionStep = Math.Max(xRange.step, 50); // avoid division by zero
|
|
|
|
// Keep increasing the division until number of lines is appropriately low
|
|
while ((xRange.max - xRange.min) / xDivisionStep > x_max_divisions)
|
|
{
|
|
xDivisionStep *= 2;
|
|
}
|
|
|
|
xRange.min = (int)floorWithFactor(xRange.min, xDivisionStep);
|
|
xRange.max = (int)ceilingWithFactor(xRange.max, xDivisionStep);
|
|
|
|
if (userRating < xRange.min)
|
|
{
|
|
this.data = this.data.Prepend((userRating.Value, 1)).ToArray();
|
|
xRange.min = userRating.Value;
|
|
}
|
|
|
|
if (userRating > xRange.max)
|
|
{
|
|
this.data = this.data.Append((userRating.Value, 1)).ToArray();
|
|
xRange.max = userRating.Value;
|
|
}
|
|
|
|
int yMax = this.data.Select(d => d.y).DefaultIfEmpty().Max();
|
|
|
|
yRange = (
|
|
0,
|
|
(int)ceilingWithFactor(yMax, significantOfNumber(yMax))
|
|
);
|
|
|
|
updateGraph();
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
xAxisContainer.X = xAxisContainer.Parent!.ToLocalSpace(chartContainer.ScreenSpaceDrawQuad.TopLeft).X;
|
|
xAxisContainer.Width = chartContainer.DrawWidth;
|
|
}
|
|
|
|
private void updateGraph() => Scheduler.AddOnce(() =>
|
|
{
|
|
xAxisContainer.Clear();
|
|
yAxisLeftContainer.Clear();
|
|
yAxisRightContainer.Clear();
|
|
gridContainer.Clear();
|
|
barsContainer.Clear();
|
|
userRatingContainer.Clear();
|
|
|
|
int xDivisions = Math.Max(1, (xRange.max - xRange.min) / xDivisionStep);
|
|
|
|
for (int step = 0; step <= xDivisions; step++)
|
|
{
|
|
gridContainer.Add(new VerticalLine
|
|
{
|
|
RelativeSizeAxes = Axes.Y,
|
|
RelativePositionAxes = Axes.X,
|
|
X = (float)step / xDivisions,
|
|
Colour = colourProvider.Background1
|
|
});
|
|
|
|
xAxisContainer.Add(new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.TopLeft,
|
|
Origin = Anchor.CentreRight,
|
|
RelativePositionAxes = Axes.X,
|
|
X = (float)step / xDivisions,
|
|
Margin = new MarginPadding { Right = -2 },
|
|
Rotation = -40,
|
|
Text = (xRange.min + xDivisionStep * step).ToString(),
|
|
UseFullGlyphHeight = false,
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Colour = colourProvider.Foreground1
|
|
});
|
|
}
|
|
|
|
for (int step = 0; step <= y_divisions; step++)
|
|
{
|
|
gridContainer.Add(new HorizontalLine
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
RelativePositionAxes = Axes.Y,
|
|
Y = (float)step / y_divisions,
|
|
Colour = colourProvider.Background1
|
|
});
|
|
|
|
yAxisLeftContainer.Add(new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.TopRight,
|
|
Origin = Anchor.CentreRight,
|
|
RelativePositionAxes = Axes.Y,
|
|
Y = 1f - (float)step / y_divisions,
|
|
Text = (yRange.min + (yRange.max - yRange.min) / y_divisions * step).ToString(),
|
|
UseFullGlyphHeight = false,
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Colour = colourProvider.Foreground1
|
|
});
|
|
|
|
yAxisRightContainer.Add(new OsuSpriteText
|
|
{
|
|
Origin = Anchor.CentreLeft,
|
|
RelativePositionAxes = Axes.Y,
|
|
Y = 1f - (float)step / y_divisions,
|
|
Text = $"{(float)step / y_divisions:P1}",
|
|
UseFullGlyphHeight = false,
|
|
Font = OsuFont.Default.With(size: 12),
|
|
Colour = colourProvider.Foreground1
|
|
});
|
|
}
|
|
|
|
foreach (var point in data)
|
|
{
|
|
barsContainer.Add(new Container
|
|
{
|
|
Origin = Anchor.BottomLeft,
|
|
Anchor = Anchor.BottomLeft,
|
|
RelativePositionAxes = Axes.X,
|
|
RelativeSizeAxes = Axes.Both,
|
|
X = pointOnGraph(point.x, point.y).X,
|
|
Height = 1 - pointOnGraph(point.x, point.y).Y,
|
|
Width = pointOnGraph(xRange.min + xRange.step, 0).X,
|
|
Colour = colourProvider.Colour0,
|
|
Masking = true,
|
|
CornerRadius = 2,
|
|
Child = new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both
|
|
}
|
|
});
|
|
}
|
|
|
|
if (userRating != null)
|
|
{
|
|
userRatingContainer.Add(new UserRatingLine(userRating.Value)
|
|
{
|
|
RelativeSizeAxes = Axes.Y,
|
|
RelativePositionAxes = Axes.X,
|
|
X = pointOnGraph(userRating.Value, 0).X,
|
|
Colour = colours.Green
|
|
});
|
|
}
|
|
|
|
if (data.Length == 0)
|
|
descriptionText.Text = "No games have been played yet.";
|
|
else if (userRating == null)
|
|
descriptionText.Text = "Play more games to get rated!";
|
|
else
|
|
{
|
|
int countPlayersBelow = data.Where(d => d.x < userRating).Sum(d => d.y);
|
|
int countPlayersAbove = data.Where(d => d.x >= userRating).Sum(d => d.y);
|
|
float p = (float)countPlayersBelow / (countPlayersBelow + countPlayersAbove);
|
|
|
|
descriptionText.Clear();
|
|
descriptionText.AddText("You are better than ");
|
|
descriptionText.AddText($"{p:P1}", s =>
|
|
{
|
|
s.Font = OsuFont.GetFont(weight: FontWeight.SemiBold);
|
|
s.Colour = colours.Green;
|
|
});
|
|
descriptionText.AddText(" of players.");
|
|
}
|
|
|
|
int currentCount = 0;
|
|
int totalCount = data.Sum(d => d.y);
|
|
|
|
cumulativePath.Points = data.Select(d =>
|
|
{
|
|
currentCount += d.y;
|
|
float p = (float)currentCount / totalCount;
|
|
return new Vector2(pointOnGraph(d.x, 0).X, 1 - p);
|
|
}).Append(new Vector2(1, 0)).ToArray();
|
|
});
|
|
|
|
private Vector2 pointOnGraph(int x, int y)
|
|
{
|
|
float xPos = ((float)x - xRange.min) / (xRange.max - xRange.min);
|
|
float yPos = 1 - ((float)y - yRange.min) / (yRange.max - yRange.min);
|
|
return new Vector2(xPos, yPos);
|
|
}
|
|
|
|
private static double significantOfNumber(double value)
|
|
{
|
|
return Math.Pow(10, Math.Floor(Math.Log10(value)));
|
|
}
|
|
|
|
private static double floorWithFactor(double value, double factor)
|
|
{
|
|
if (value == 0 || factor == 0)
|
|
return 0;
|
|
|
|
return Math.Floor(value / factor) * factor;
|
|
}
|
|
|
|
private static double ceilingWithFactor(double value, double factor)
|
|
{
|
|
if (value == 0 || factor == 0)
|
|
return 0;
|
|
|
|
return Math.Ceiling(value / factor) * factor;
|
|
}
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
|
{
|
|
return chartContainer.DrawRectangle.Inflate(20).Contains(chartContainer.ToLocalSpace(screenSpacePos));
|
|
}
|
|
|
|
protected override bool OnHover(HoverEvent e)
|
|
{
|
|
hoverMarker.FadeTo(1f, 200);
|
|
return true;
|
|
}
|
|
|
|
protected override void OnHoverLost(HoverLostEvent e)
|
|
{
|
|
hoverMarker.FadeTo(0f, 200);
|
|
}
|
|
|
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
|
{
|
|
float minDistToCursor = float.MaxValue;
|
|
Vector2 closestPointToCursor = Vector2.Zero;
|
|
Color4 closestColourToCursor = Color4.White;
|
|
int closestRatingToCursor = 0;
|
|
string closestValueToCursor = string.Empty;
|
|
|
|
if (userRating != null)
|
|
{
|
|
Vector2 userRatingPos1 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, 0) * userRatingContainer.DrawSize);
|
|
Vector2 userRatingPos2 = userRatingContainer.ToScreenSpace(pointOnGraph(userRating.Value, yRange.max) * userRatingContainer.DrawSize);
|
|
|
|
minDistToCursor = Vector2.Distance(e.ScreenSpaceMousePosition, userRatingPos1);
|
|
closestPointToCursor = userRatingPos1;
|
|
closestColourToCursor = colours.Green;
|
|
closestRatingToCursor = userRating.Value;
|
|
closestValueToCursor = $"Your rating ({userRating})";
|
|
|
|
float d = Vector2.Distance(e.ScreenSpaceMousePosition, userRatingPos2);
|
|
|
|
if (d < minDistToCursor)
|
|
{
|
|
minDistToCursor = d;
|
|
closestPointToCursor = userRatingPos2;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
Vector2 pos = barsContainer.ToScreenSpace(pointOnGraph(data[i].x, data[i].y) * barsContainer.DrawSize);
|
|
float d = Vector2.Distance(e.ScreenSpaceMousePosition, pos);
|
|
|
|
if (d < minDistToCursor)
|
|
{
|
|
minDistToCursor = d;
|
|
closestPointToCursor = pos;
|
|
closestColourToCursor = colourProvider.Colour0;
|
|
closestRatingToCursor = data[i].x;
|
|
closestValueToCursor = $"Players: {data[i].y}";
|
|
}
|
|
}
|
|
|
|
int currentCount = 0;
|
|
int totalCount = data.Sum(p => p.y);
|
|
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
currentCount += data[i].y;
|
|
|
|
Vector2 pos = cumulativePath.ToScreenSpace(cumulativePath.Vertices[i] + new Vector2(2));
|
|
float d = Vector2.Distance(e.ScreenSpaceMousePosition, pos);
|
|
|
|
if (d < minDistToCursor)
|
|
{
|
|
minDistToCursor = d;
|
|
closestPointToCursor = pos;
|
|
closestColourToCursor = colours.Yellow;
|
|
closestRatingToCursor = data[i].x;
|
|
closestValueToCursor = $"Cumulative: {(float)currentCount / totalCount:P1}";
|
|
}
|
|
}
|
|
|
|
if (minDistToCursor == float.MaxValue)
|
|
TooltipContent = new RatingDistributionGraphTooltipData();
|
|
else
|
|
{
|
|
TooltipContent = new RatingDistributionGraphTooltipData
|
|
{
|
|
Colour = closestColourToCursor,
|
|
Position = closestPointToCursor,
|
|
Rating = closestRatingToCursor,
|
|
Value = closestValueToCursor,
|
|
};
|
|
}
|
|
|
|
hoverMarker.Position = gridContainer.ToLocalSpace(TooltipContent.Position);
|
|
hoverMarkerFill.Colour = TooltipContent.Colour;
|
|
|
|
return true;
|
|
}
|
|
|
|
public ITooltip<RatingDistributionGraphTooltipData> GetCustomTooltip() => new RatingDistributionGraphTooltip();
|
|
|
|
public RatingDistributionGraphTooltipData TooltipContent { get; private set; } = new RatingDistributionGraphTooltipData();
|
|
|
|
/// <summary>
|
|
/// A simple vertical line that always remains 1px in size.
|
|
/// </summary>
|
|
private partial class VerticalLine : Box
|
|
{
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
if (Precision.DefinitelyBigger(Parent!.ScreenSpaceDrawQuad.Width, 0))
|
|
Width = Parent.DrawWidth / Parent.ScreenSpaceDrawQuad.Width;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A simple horizontal line that always remains 1px in size.
|
|
/// </summary>
|
|
private partial class HorizontalLine : Box
|
|
{
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
if (Precision.DefinitelyBigger(Parent!.ScreenSpaceDrawQuad.Height, 0))
|
|
Height = Parent!.DrawHeight / Parent.ScreenSpaceDrawQuad.Height;
|
|
}
|
|
}
|
|
|
|
private partial class UserRatingLine : CompositeDrawable
|
|
{
|
|
public UserRatingLine(int rating)
|
|
{
|
|
InternalChildren = new Drawable[]
|
|
{
|
|
new Box
|
|
{
|
|
Anchor = Anchor.TopCentre,
|
|
Origin = Anchor.TopCentre,
|
|
RelativeSizeAxes = Axes.Y,
|
|
Width = 2,
|
|
},
|
|
new Circle
|
|
{
|
|
Anchor = Anchor.TopCentre,
|
|
Origin = Anchor.Centre,
|
|
Size = new Vector2(8),
|
|
},
|
|
new Circle
|
|
{
|
|
Anchor = Anchor.BottomCentre,
|
|
Origin = Anchor.Centre,
|
|
Size = new Vector2(8),
|
|
},
|
|
new OsuSpriteText
|
|
{
|
|
Anchor = Anchor.TopCentre,
|
|
Origin = Anchor.BottomCentre,
|
|
Y = -4,
|
|
Text = $"Your rating ({rating})",
|
|
Font = OsuFont.Torus.With(size: 12),
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
private partial class PointPath : SmoothPath
|
|
{
|
|
private Vector2[] points = [];
|
|
|
|
public Vector2[] Points
|
|
{
|
|
get => points;
|
|
set
|
|
{
|
|
points = value;
|
|
verticesCache.Invalidate();
|
|
}
|
|
}
|
|
|
|
private Vector2 offset;
|
|
|
|
public Vector2 Offset
|
|
{
|
|
get => offset;
|
|
set
|
|
{
|
|
offset = value;
|
|
verticesCache.Invalidate();
|
|
}
|
|
}
|
|
|
|
private readonly LayoutValue verticesCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
|
|
|
|
public PointPath()
|
|
{
|
|
AddLayout(verticesCache);
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
if (!verticesCache.IsValid)
|
|
{
|
|
updateVertices();
|
|
verticesCache.Validate();
|
|
}
|
|
}
|
|
|
|
private void updateVertices()
|
|
{
|
|
ClearVertices();
|
|
|
|
for (int i = 0; i < Points.Length; i++)
|
|
AddVertex(Points[i] * (Parent!.DrawSize + offset));
|
|
}
|
|
}
|
|
|
|
private partial class RatingDistributionGraphTooltip : VisibilityContainer, ITooltip<RatingDistributionGraphTooltipData>
|
|
{
|
|
private readonly OsuSpriteText ratingText;
|
|
private readonly Drawable valueColour;
|
|
private readonly OsuSpriteText valueText;
|
|
|
|
private RatingDistributionGraphTooltipData content = new RatingDistributionGraphTooltipData();
|
|
private bool instantMove = true;
|
|
|
|
public RatingDistributionGraphTooltip()
|
|
{
|
|
AutoSizeAxes = Axes.Both;
|
|
|
|
InternalChild = new Container
|
|
{
|
|
AutoSizeAxes = Axes.Both,
|
|
Masking = true,
|
|
CornerRadius = 3,
|
|
Children = new Drawable[]
|
|
{
|
|
new Box
|
|
{
|
|
RelativeSizeAxes = Axes.Both,
|
|
Colour = Color4.Black,
|
|
Alpha = 0.7f
|
|
},
|
|
new FillFlowContainer
|
|
{
|
|
AutoSizeAxes = Axes.Both,
|
|
Padding = new MarginPadding(8),
|
|
Direction = FillDirection.Vertical,
|
|
Spacing = new Vector2(3),
|
|
Children = new Drawable[]
|
|
{
|
|
ratingText = new OsuSpriteText
|
|
{
|
|
Font = OsuFont.Torus.With(weight: FontWeight.SemiBold)
|
|
},
|
|
new FillFlowContainer
|
|
{
|
|
AutoSizeAxes = Axes.Both,
|
|
Direction = FillDirection.Horizontal,
|
|
Spacing = new Vector2(3),
|
|
Children = new[]
|
|
{
|
|
valueColour = new Box
|
|
{
|
|
Size = new Vector2(12)
|
|
},
|
|
valueText = new OsuSpriteText
|
|
{
|
|
Font = OsuFont.Torus.With(size: 12)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
public void SetContent(RatingDistributionGraphTooltipData content)
|
|
{
|
|
this.content = content;
|
|
|
|
ratingText.Text = content.Rating.ToString();
|
|
valueColour.Colour = content.Colour;
|
|
valueText.Text = content.Value;
|
|
}
|
|
|
|
public void Move(Vector2 pos)
|
|
{
|
|
pos = Parent!.ToLocalSpace(content.Position) - new Vector2(DrawWidth + 10, 0);
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
public class RatingDistributionGraphTooltipData
|
|
{
|
|
public Color4 Colour;
|
|
public Vector2 Position;
|
|
|
|
public int Rating;
|
|
public string Value = string.Empty;
|
|
}
|
|
}
|
|
}
|