diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs index d5fc1b606b..0d7fa6c334 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuEditPlayfield : OsuPlayfield { protected override CursorContainer CreateCursor() => null; + + protected override bool ProxyApproachCircles => false; } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f03acb2fa0..0dc6feee75 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ProvidingUserCursor => true; + // Todo: This should not be a thing, but is currently required for the editor + // https://github.com/ppy/osu-framework/issues/1283 + protected virtual bool ProxyApproachCircles => true; + public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public override Vector2 Size @@ -80,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI h.Depth = (float)h.HitObject.StartTime; var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null) + if (c != null && ProxyApproachCircles) approachCircles.Add(c.ProxiedLayer.CreateProxy()); base.Add(h); diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs new file mode 100644 index 0000000000..489fba6501 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using System.Collections.Generic; +using System; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseRankGraph : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankGraph), + typeof(LineGraph) + }; + + public TestCaseRankGraph() + { + RankGraph graph; + + var data = new int[89]; + var dataWithZeros = new int[89]; + var smallData = new int[89]; + + for (int i = 0; i < 89; i++) + data[i] = dataWithZeros[i] = (i + 1) * 1000; + + for (int i = 20; i < 60; i++) + dataWithZeros[i] = 0; + + for (int i = 79; i < 89; i++) + smallData[i] = 100000 - i * 1000; + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 150), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + graph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + } + } + }); + + AddStep("null user", () => graph.User.Value = null); + AddStep("rank only", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 123456, + PP = 12345, + } + }; + }); + + AddStep("with rank history", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = data, + } + }; + }); + + AddStep("with zero values", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = dataWithZeros, + } + }; + }); + + AddStep("small amount of data", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 12000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = smallData, + } + }; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 697b84941c..38d59f03b5 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -2,14 +2,25 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Overlays.Profile; using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseUserProfile : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(UserProfileOverlay), + typeof(RankGraph), + typeof(LineGraph), + }; + public TestCaseUserProfile() { var profile = new UserProfileOverlay(); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b79162c00c..ff012bb6e2 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -134,6 +134,7 @@ + diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index aa9256e576..ea74c67f6c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Caching; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -47,7 +48,16 @@ namespace osu.Game.Graphics.UserInterface set { values = value.ToArray(); - applyPath(); + + float max = values.Max(), min = values.Min(); + if (MaxValue > max) max = MaxValue.Value; + if (MinValue < min) min = MinValue.Value; + + ActualMaxValue = max; + ActualMinValue = min; + + pathCached.Invalidate(); + maskingContainer.Width = 0; maskingContainer.ResizeWidthTo(1, transform_duration, Easing.OutQuint); } @@ -63,13 +73,28 @@ namespace osu.Game.Graphics.UserInterface }); } + private bool pending; + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { - if ((invalidation & Invalidation.DrawSize) != 0) - applyPath(); + if ((invalidation & Invalidation.DrawSize) > 0) + pathCached.Invalidate(); + return base.Invalidate(invalidation, source, shallPropagate); } + private Cached pathCached = new Cached(); + + protected override void Update() + { + base.Update(); + if (!pathCached.IsValid) + { + applyPath(); + pathCached.Validate(); + } + } + private void applyPath() { path.ClearVertices(); @@ -77,13 +102,6 @@ namespace osu.Game.Graphics.UserInterface int count = Math.Max(values.Length, DefaultValueCount); - float max = values.Max(), min = values.Min(); - if (MaxValue > max) max = MaxValue.Value; - if (MinValue < min) min = MinValue.Value; - - ActualMaxValue = max; - ActualMinValue = min; - for (int i = 0; i < values.Length; i++) { float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4ddd6c498e..a706799664 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -27,8 +27,9 @@ namespace osu.Game.Overlays.Profile private readonly OsuTextFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; + private readonly RankGraph rankGraph; - private readonly Container coverContainer, chartContainer, supporterTag; + private readonly Container coverContainer, supporterTag; private readonly Sprite levelBadge; private readonly SpriteText levelText; private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; @@ -273,7 +274,7 @@ namespace osu.Game.Overlays.Profile } } }, - chartContainer = new Container + new Container { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomCentre, @@ -285,6 +286,10 @@ namespace osu.Game.Overlays.Profile { Colour = Color4.Black.Opacity(0.25f), RelativeSizeAxes = Axes.Both + }, + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both } } } @@ -303,11 +308,7 @@ namespace osu.Game.Overlays.Profile public User User { - get - { - return user; - } - + get { return user; } set { user = value; @@ -420,7 +421,7 @@ namespace osu.Game.Overlays.Profile gradeSPlus.DisplayCount = 0; gradeSSPlus.DisplayCount = 0; - chartContainer.Add(new RankChart(user) { RelativeSizeAxes = Axes.Both }); + rankGraph.User.Value = user; } } diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankGraph.cs similarity index 62% rename from osu.Game/Overlays/Profile/RankChart.cs rename to osu.Game/Overlays/Profile/RankGraph.cs index 8190ef3c62..d216789b1a 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -14,30 +14,39 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; +using System.Collections.Generic; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile { - public class RankChart : Container + public class RankGraph : Container { + private const float primary_textsize = 25; + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + private readonly SpriteText rankText, performanceText, relativeText; private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; - private readonly int[] ranks; + private KeyValuePair[] ranks; + public Bindable User = new Bindable(); - private const float primary_textsize = 25, secondary_textsize = 13, padding = 10; - - private readonly User user; - - public RankChart(User user) + public RankGraph() { - this.user = user; - - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; - ranks = userRanks.SkipWhile(x => x == 0).ToArray(); - Padding = new MarginPadding { Vertical = padding }; Children = new Drawable[] { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + TextSize = 14, + Font = @"Exo2.0-RegularItalic", + }, rankText = new OsuSpriteText { Anchor = Anchor.TopCentre, @@ -60,89 +69,103 @@ namespace osu.Game.Overlays.Profile Font = @"Exo2.0-RegularItalic", TextSize = secondary_textsize }, - }; - - if (ranks.Length > 0) - { - Add(graph = new RankChartLineGraph + graph = new RankChartLineGraph { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, + Height = 75, Y = -secondary_textsize, - DefaultValueCount = ranks.Length, - }); + Alpha = 0, + } + }; - graph.OnBallMove += showHistoryRankTexts; - } - } + graph.OnBallMove += showHistoryRankTexts; - private void updateRankTexts() - { - rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank"; - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - relativeText.Text = user.CountryRank > 0 ? $"{user.Country?.FullName} #{user.CountryRank:#,0}" : $"{user.Country?.FullName}"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = ranks[dayIndex] > 0 ? $"#{ranks[dayIndex]:#,0}" : "no rank"; - dayIndex++; - relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago"; - //plural should be handled in a general way + User.ValueChanged += userChanged; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - if (graph != null) + graph.Colour = colours.Yellow; + } + + private void userChanged(User user) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (user == null) { - graph.Colour = colours.Yellow; - // use logarithmic coordinates - graph.Values = ranks.Select(x => x == 0 ? float.MinValue : -(float)Math.Log(x)); + rankText.Text = string.Empty; + performanceText.Text = string.Empty; + relativeText.Text = string.Empty; + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); graph.SetStaticBallPosition(); } + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + updateRankTexts(); } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) + private void updateRankTexts() { - if ((invalidation & Invalidation.DrawSize) != 0) - { - graph.Height = DrawHeight - padding * 2 - primary_textsize - secondary_textsize * 2; - } + rankText.Text = User.Value.Statistics.Rank > 0 ? $"#{User.Value.Statistics.Rank:#,0}" : "no rank"; + performanceText.Text = User.Value.Statistics.PP != null ? $"{User.Value.Statistics.PP:#,0}pp" : string.Empty; + relativeText.Text = $"{User.Value.Country?.FullName} #{User.Value.CountryRank:#,0}"; + } - return base.Invalidate(invalidation, source, shallPropagate); + private void showHistoryRankTexts(int dayIndex) + { + rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; + relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } protected override bool OnHover(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); - graph?.ShowBall(); + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(state.Mouse.Position.X); + graph.ShowBall(); + } return base.OnHover(state); } protected override bool OnMouseMove(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); + if (ranks?.Length > 1) + graph.UpdateBallPosition(state.Mouse.Position.X); + return base.OnMouseMove(state); } protected override void OnHoverLost(InputState state) { - if (graph != null) + if (ranks?.Length > 1) { graph.HideBall(); updateRankTexts(); } + base.OnHoverLost(state); } private class RankChartLineGraph : LineGraph { - private const double fade_duration = 200; - private readonly CircularContainer staticBall; private readonly CircularContainer movingBall; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1241017b9b..e78d10cc4f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -503,7 +503,7 @@ - +