1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 09:47:52 +08:00

Merge pull request #8304 from smoogipoo/results-circle

Implement a circle that displays the user's accuracy
This commit is contained in:
Dean Herbert 2020-03-17 18:59:40 +09:00 committed by GitHub
commit 8396fcfd35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 864 additions and 32 deletions

View File

@ -0,0 +1,155 @@
// 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 NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Expanded.Accuracy;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Ranking
{
public class TestSceneAccuracyCircle : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(AccuracyCircle),
typeof(RankBadge),
typeof(RankNotch),
typeof(RankText),
typeof(SmoothCircularProgress)
};
[Test]
public void TestDRank()
{
var score = createScore();
score.Accuracy = 0.5;
score.Rank = ScoreRank.D;
addCircleStep(score);
}
[Test]
public void TestCRank()
{
var score = createScore();
score.Accuracy = 0.75;
score.Rank = ScoreRank.C;
addCircleStep(score);
}
[Test]
public void TestBRank()
{
var score = createScore();
score.Accuracy = 0.85;
score.Rank = ScoreRank.B;
addCircleStep(score);
}
[Test]
public void TestARank()
{
var score = createScore();
score.Accuracy = 0.925;
score.Rank = ScoreRank.A;
addCircleStep(score);
}
[Test]
public void TestSRank()
{
var score = createScore();
score.Accuracy = 0.975;
score.Rank = ScoreRank.S;
addCircleStep(score);
}
[Test]
public void TestAlmostSSRank()
{
var score = createScore();
score.Accuracy = 0.9999;
score.Rank = ScoreRank.S;
addCircleStep(score);
}
[Test]
public void TestSSRank()
{
var score = createScore();
score.Accuracy = 1;
score.Rank = ScoreRank.X;
addCircleStep(score);
}
private void addCircleStep(ScoreInfo score) => AddStep("add panel", () =>
{
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500, 700),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333"))
}
}
},
new AccuracyCircle(score)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(230)
}
};
});
private ScoreInfo createScore() => new ScoreInfo
{
User = new User
{
Id = 2,
Username = "peppy",
},
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
TotalScore = 2845370,
Accuracy = 0.95,
MaxCombo = 999,
Rank = ScoreRank.S,
Date = DateTimeOffset.Now,
Statistics =
{
{ HitResult.Miss, 1 },
{ HitResult.Meh, 50 },
{ HitResult.Good, 100 },
{ HitResult.Great, 300 },
}
};
}
}

View File

@ -3,6 +3,7 @@
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Graphics namespace osu.Game.Graphics
@ -37,6 +38,35 @@ namespace osu.Game.Graphics
} }
} }
/// <summary>
/// Retrieves the colour for a <see cref="ScoreRank"/>.
/// </summary>
public static Color4 ForRank(ScoreRank rank)
{
switch (rank)
{
case ScoreRank.XH:
case ScoreRank.X:
return Color4Extensions.FromHex(@"ce1c9d");
case ScoreRank.SH:
case ScoreRank.S:
return Color4Extensions.FromHex(@"00a8b5");
case ScoreRank.A:
return Color4Extensions.FromHex(@"7cce14");
case ScoreRank.B:
return Color4Extensions.FromHex(@"e3b130");
case ScoreRank.C:
return Color4Extensions.FromHex(@"f18252");
default:
return Color4Extensions.FromHex(@"e95353");
}
}
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff"); public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff"); public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");

View File

@ -43,6 +43,18 @@ namespace osu.Game.Graphics.Sprites
set => blurredText.Colour = value; set => blurredText.Colour = value;
} }
public Vector2 Spacing
{
get => spriteText.Spacing;
set => spriteText.Spacing = blurredText.Spacing = value;
}
public bool UseFullGlyphHeight
{
get => spriteText.UseFullGlyphHeight;
set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
}
public GlowingSpriteText() public GlowingSpriteText()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;

View File

@ -28,7 +28,7 @@ namespace osu.Game.Online.Leaderboards
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
FillAspectRatio = 2; FillAspectRatio = 2;
var rankColour = getRankColour(); var rankColour = OsuColour.ForRank(rank);
InternalChild = new DrawSizePreservingFillContainer InternalChild = new DrawSizePreservingFillContainer
{ {
TargetDrawSize = new Vector2(64, 32), TargetDrawSize = new Vector2(64, 32),
@ -59,7 +59,7 @@ namespace osu.Game.Online.Leaderboards
Padding = new MarginPadding { Top = 5 }, Padding = new MarginPadding { Top = 5 },
Colour = getRankNameColour(), Colour = getRankNameColour(),
Font = OsuFont.Numeric.With(size: 25), Font = OsuFont.Numeric.With(size: 25),
Text = getRankName(), Text = GetRankName(rank),
ShadowColour = Color4.Black.Opacity(0.3f), ShadowColour = Color4.Black.Opacity(0.3f),
ShadowOffset = new Vector2(0, 0.08f), ShadowOffset = new Vector2(0, 0.08f),
Shadow = true, Shadow = true,
@ -69,36 +69,7 @@ namespace osu.Game.Online.Leaderboards
}; };
} }
private string getRankName() => rank.GetDescription().TrimEnd('+'); public static string GetRankName(ScoreRank rank) => rank.GetDescription().TrimEnd('+');
/// <summary>
/// Retrieves the grade background colour.
/// </summary>
private Color4 getRankColour()
{
switch (rank)
{
case ScoreRank.XH:
case ScoreRank.X:
return Color4Extensions.FromHex(@"ce1c9d");
case ScoreRank.SH:
case ScoreRank.S:
return Color4Extensions.FromHex(@"00a8b5");
case ScoreRank.A:
return Color4Extensions.FromHex(@"7cce14");
case ScoreRank.B:
return Color4Extensions.FromHex(@"e3b130");
case ScoreRank.C:
return Color4Extensions.FromHex(@"f18252");
default:
return Color4Extensions.FromHex(@"e95353");
}
}
/// <summary> /// <summary>
/// Retrieves the grade text colour. /// Retrieves the grade text colour.

View File

@ -0,0 +1,253 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// The component that displays the player's accuracy on the results screen.
/// </summary>
public class AccuracyCircle : CompositeDrawable
{
/// <summary>
/// Duration for the transforms causing this component to appear.
/// </summary>
public const double APPEAR_DURATION = 200;
/// <summary>
/// Delay before the accuracy circle starts filling.
/// </summary>
public const double ACCURACY_TRANSFORM_DELAY = 450;
/// <summary>
/// Duration for the accuracy circle fill.
/// </summary>
public const double ACCURACY_TRANSFORM_DURATION = 3000;
/// <summary>
/// Delay after <see cref="ACCURACY_TRANSFORM_DURATION"/> for the rank text (A/B/C/D/S/SS) to appear.
/// </summary>
public const double TEXT_APPEAR_DELAY = ACCURACY_TRANSFORM_DURATION / 2;
/// <summary>
/// Delay before the rank circles start filling.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DELAY = 150;
/// <summary>
/// Duration for the rank circle fills.
/// </summary>
public const double RANK_CIRCLE_TRANSFORM_DURATION = 800;
/// <summary>
/// Relative width of the rank circles.
/// </summary>
public const float RANK_CIRCLE_RADIUS = 0.06f;
/// <summary>
/// Relative width of the circle showing the accuracy.
/// </summary>
private const float accuracy_circle_radius = 0.2f;
/// <summary>
/// SS is displayed as a 1% region, otherwise it would be invisible.
/// </summary>
private const double virtual_ss_percentage = 0.01;
/// <summary>
/// The easing for the circle filling transforms.
/// </summary>
public static readonly Easing ACCURACY_TRANSFORM_EASING = Easing.OutPow10;
private readonly ScoreInfo score;
private SmoothCircularProgress accuracyCircle;
private SmoothCircularProgress innerMask;
private Container<RankBadge> badges;
private RankText rankText;
public AccuracyCircle(ScoreInfo score)
{
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
new SmoothCircularProgress
{
Name = "Background circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(47),
Alpha = 0.5f,
InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle
Current = { Value = 1 },
},
accuracyCircle = new SmoothCircularProgress
{
Name = "Accuracy circle",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#7CF6FF"), Color4Extensions.FromHex("#BAFFA9")),
InnerRadius = accuracy_circle_radius,
},
new BufferedContainer
{
Name = "Graded circles",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Padding = new MarginPadding(2),
Children = new Drawable[]
{
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#BE0089"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#0096A2"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 1 - virtual_ss_percentage }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#72C904"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.95f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#D99D03"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.9f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#EA7948"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.8f }
},
new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("#FF5858"),
InnerRadius = RANK_CIRCLE_RADIUS,
Current = { Value = 0.7f }
},
new RankNotch(0),
new RankNotch((float)(1 - virtual_ss_percentage)),
new RankNotch(0.95f),
new RankNotch(0.9f),
new RankNotch(0.8f),
new RankNotch(0.7f),
new BufferedContainer
{
Name = "Graded circle mask",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(1),
Blending = new BlendingParameters
{
Source = BlendingType.DstColor,
Destination = BlendingType.OneMinusSrcAlpha,
SourceAlpha = BlendingType.One,
DestinationAlpha = BlendingType.SrcAlpha
},
Child = innerMask = new SmoothCircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = RANK_CIRCLE_RADIUS - 0.01f,
}
}
}
},
badges = new Container<RankBadge>
{
Name = "Rank badges",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Vertical = -15, Horizontal = -20 },
Children = new[]
{
new RankBadge(1f, ScoreRank.X),
new RankBadge(0.95f, ScoreRank.S),
new RankBadge(0.9f, ScoreRank.A),
new RankBadge(0.8f, ScoreRank.B),
new RankBadge(0.7f, ScoreRank.C),
}
},
rankText = new RankText(score.Rank)
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.ScaleTo(0).Then().ScaleTo(1, APPEAR_DURATION, Easing.OutQuint);
using (BeginDelayedSequence(RANK_CIRCLE_TRANSFORM_DELAY, true))
innerMask.FillTo(1f, RANK_CIRCLE_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY, true))
{
double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
foreach (var badge in badges)
{
if (badge.Accuracy > score.Accuracy)
continue;
using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(1 - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION, true))
badge.Appear();
}
using (BeginDelayedSequence(TEXT_APPEAR_DELAY, true))
rankText.Appear();
}
}
private double inverseEasing(Easing easing, double targetValue)
{
double test = 0;
double result = 0;
int count = 2;
while (Math.Abs(result - targetValue) > 0.005)
{
int dir = Math.Sign(targetValue - result);
test += dir * 1.0 / count;
result = Interpolation.ApplyEasing(easing, test);
count++;
}
return test;
}
}
}

View File

@ -0,0 +1,99 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// Contains a <see cref="DrawableRank"/> that is positioned around the <see cref="AccuracyCircle"/>.
/// </summary>
public class RankBadge : CompositeDrawable
{
/// <summary>
/// The accuracy value corresponding to the <see cref="ScoreRank"/> displayed by this badge.
/// </summary>
public readonly float Accuracy;
private readonly ScoreRank rank;
private Drawable rankContainer;
private Drawable overlay;
/// <summary>
/// Creates a new <see cref="RankBadge"/>.
/// </summary>
/// <param name="accuracy">The accuracy value corresponding to <paramref name="rank"/>.</param>
/// <param name="rank">The <see cref="ScoreRank"/> to be displayed in this <see cref="RankBadge"/>.</param>
public RankBadge(float accuracy, ScoreRank rank)
{
Accuracy = accuracy;
this.rank = rank;
RelativeSizeAxes = Axes.Both;
Alpha = 0;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = rankContainer = new Container
{
Origin = Anchor.Centre,
Size = new Vector2(28, 14),
Children = new[]
{
new DrawableRank(rank),
overlay = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = OsuColour.ForRank(rank).Opacity(0.2f),
Radius = 10,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
}
}
}
};
}
/// <summary>
/// Shows this <see cref="RankBadge"/>.
/// </summary>
public void Appear()
{
this.FadeIn(50);
overlay.FadeIn().FadeOut(500, Easing.In);
}
protected override void Update()
{
base.Update();
// Starts at -90deg (top) and moves counter-clockwise by the accuracy
rankContainer.Position = circlePosition(-MathF.PI / 2 - (1 - Accuracy) * MathF.PI * 2);
}
private Vector2 circlePosition(float t)
=> DrawSize / 2 + new Vector2(MathF.Cos(t), MathF.Sin(t)) * DrawSize / 2;
}
}

View File

@ -0,0 +1,49 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// A solid "notch" of the <see cref="AccuracyCircle"/> that appears at the ends of the rank circles to add separation.
/// </summary>
public class RankNotch : CompositeDrawable
{
private readonly float position;
public RankNotch(float position)
{
this.position = position;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Rotation = position * 360f,
Child = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
Width = 1f,
Colour = OsuColour.Gray(0.3f),
EdgeSmoothness = new Vector2(1f)
}
};
}
}
}

View File

@ -0,0 +1,137 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// The text that appears in the middle of the <see cref="AccuracyCircle"/> displaying the user's rank.
/// </summary>
public class RankText : CompositeDrawable
{
private readonly ScoreRank rank;
private BufferedContainer flash;
private BufferedContainer superFlash;
private GlowingSpriteText rankText;
public RankText(ScoreRank rank)
{
this.rank = rank;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Alpha = 0;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChildren = new Drawable[]
{
rankText = new GlowingSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
GlowColour = OsuColour.ForRank(rank),
Spacing = new Vector2(-15, 0),
Text = DrawableRank.GetRankName(rank),
Font = OsuFont.Numeric.With(size: 76),
UseFullGlyphHeight = false
},
superFlash = new BufferedContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(85),
Size = new Vector2(600),
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Children = new[]
{
new Box
{
Colour = Color4.White,
Size = new Vector2(150),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
},
},
flash = new BufferedContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlurSigma = new Vector2(35),
BypassAutoSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Blending = BlendingParameters.Additive,
Alpha = 0,
Size = new Vector2(2f), // increase buffer size to allow for scale
Scale = new Vector2(1.8f),
Children = new[]
{
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(-15, 0),
Text = DrawableRank.GetRankName(rank),
Font = OsuFont.Numeric.With(size: 76),
UseFullGlyphHeight = false,
Shadow = false
},
},
},
};
}
public void Appear()
{
this.FadeIn();
if (rank < ScoreRank.A)
{
this
.MoveToOffset(new Vector2(0, -20))
.MoveToOffset(new Vector2(0, 20), 200, Easing.OutBounce);
if (rank <= ScoreRank.D)
{
this.Delay(700)
.RotateTo(5, 150, Easing.In)
.MoveToOffset(new Vector2(0, 3), 150, Easing.In);
}
this.FadeInFromZero(200, Easing.OutQuint);
return;
}
flash.Colour = OsuColour.ForRank(rank);
flash.FadeIn().Then().FadeOut(1200, Easing.OutQuint);
if (rank >= ScoreRank.S)
rankText.ScaleTo(1.05f).ScaleTo(1, 3000, Easing.OutQuint);
if (rank >= ScoreRank.X)
{
flash.FadeIn().Then().FadeOut(3000);
superFlash.FadeIn().Then().FadeOut(800, Easing.OutQuint);
}
}
}
}

View File

@ -0,0 +1,126 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Screens.Ranking.Expanded.Accuracy
{
/// <summary>
/// Contains a <see cref="CircularProgress"/> with smoothened edges.
/// </summary>
public class SmoothCircularProgress : CompositeDrawable
{
public Bindable<double> Current
{
get => progress.Current;
set => progress.Current = value;
}
public float InnerRadius
{
get => progress.InnerRadius;
set
{
progress.InnerRadius = value;
innerSmoothingContainer.Size = new Vector2(1 - value);
smoothingWedge.Height = value / 2;
}
}
private readonly CircularProgress progress;
private readonly Container innerSmoothingContainer;
private readonly Drawable smoothingWedge;
public SmoothCircularProgress()
{
Container smoothingWedgeContainer;
InternalChild = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
progress = new CircularProgress { RelativeSizeAxes = Axes.Both },
smoothingWedgeContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Child = smoothingWedge = new Box
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Width = 1f,
EdgeSmoothness = new Vector2(2, 0),
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-1),
Child = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
Masking = true,
BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
Blending = new BlendingParameters
{
AlphaEquation = BlendingEquation.ReverseSubtract,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
innerSmoothingContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = Vector2.Zero,
Padding = new MarginPadding(-1),
Child = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
BorderThickness = 2,
BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f),
Masking = true,
Blending = new BlendingParameters
{
AlphaEquation = BlendingEquation.ReverseSubtract,
},
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
},
}
};
Current.BindValueChanged(c =>
{
smoothingWedgeContainer.Alpha = c.NewValue > 0 ? 1 : 0;
smoothingWedgeContainer.Rotation = (float)(360 * c.NewValue);
}, true);
}
public TransformSequence<CircularProgress> FillTo(double newValue, double duration = 0, Easing easing = Easing.None)
=> progress.FillTo(newValue, duration, easing);
}
}