From 5b9378d52b4f3bad782490ae45caa71f39de1262 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sat, 19 Jun 2021 23:12:57 +0200 Subject: [PATCH 01/12] Initial redesigns --- .../Statistics/TestSceneJudgementChart.cs | 2 + .../Statistics/JudgementChart.cs | 269 ++++++++++-------- 2 files changed, 148 insertions(+), 123 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs index 0c5fd6c61..601132a82 100644 --- a/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki.Tests/Statistics/TestSceneJudgementChart.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Sentakki.Tests.Statistics [TestFixture] public class TestSceneJudgementChart : OsuTestScene { + protected override Ruleset CreateRuleset() => new SentakkiRuleset(); + private List testevents = new List { // Tap diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 533935001..43ff88237 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -1,11 +1,16 @@ -using System; using System.Collections.Generic; using System.Linq; 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.OpenGL.Textures; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -13,176 +18,128 @@ using osu.Game.Rulesets.Sentakki.Objects; using osuTK; using osuTK.Graphics; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace osu.Game.Rulesets.Sentakki.Statistics { - public class JudgementChart : CompositeDrawable + public class JudgementChart : FillFlowContainer { private const double entry_animation_duration = 150; private const double bar_fill_duration = 3000; + public JudgementChart(List hitEvents) { Origin = Anchor.Centre; Anchor = Anchor.Centre; Size = new Vector2(500, 250); + //RelativeSizeAxes = Axes.Both; + AddRangeInternal(new Drawable[]{ - new NoteEntry + new NoteEntry(0) { ObjectName = "Tap", HitEvents = hitEvents.Where(e=> e.HitObject is Tap x && !x.Break).ToList(), - Position = new Vector2(0, 0), - InitialLifetimeOffset = entry_animation_duration * 0 }, - new NoteEntry + new NoteEntry(1) { ObjectName = "Hold", HitEvents = hitEvents.Where(e => (e.HitObject is Hold x || e.HitObject is Hold.HoldHead) && !(e.HitObject as SentakkiLanedHitObject).Break).ToList(), - Position = new Vector2(0, .16f), - InitialLifetimeOffset = entry_animation_duration * 1 }, - new NoteEntry + new NoteEntry(2) { ObjectName = "Slide", HitEvents = hitEvents.Where(e => e.HitObject is SlideBody).ToList(), - Position = new Vector2(0, .32f), - InitialLifetimeOffset = entry_animation_duration * 2 }, - new NoteEntry + new NoteEntry(3) { ObjectName = "Touch", HitEvents = hitEvents.Where(e => e.HitObject is Touch).ToList(), - Position = new Vector2(0, .48f), - InitialLifetimeOffset = entry_animation_duration * 3 }, - new NoteEntry + new NoteEntry(4) { ObjectName = "Touch Hold", HitEvents = hitEvents.Where(e => e.HitObject is TouchHold).ToList(), - Position = new Vector2(0, .64f), - InitialLifetimeOffset = entry_animation_duration * 4 }, - new NoteEntry + new NoteEntry(5) { ObjectName = "Break", HitEvents = hitEvents.Where(e => e.HitObject is SentakkiLanedHitObject x && x.Break).ToList(), - Position = new Vector2(0, .80f), - InitialLifetimeOffset = entry_animation_duration * 5 }, }); } - public class NoteEntry : Container + + private class NoteEntry : CompositeDrawable { + public NoteEntry(int entryIndex) + { + AlwaysPresent = true; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + RelativeSizeAxes = Axes.Both; + Scale = new Vector2(1, 0); + + Masking = true; + BorderThickness = 2; + CornerRadius = 5; + CornerExponent = 2.5f; + + Height = 1f / 6f; + + InitialLifetimeOffset = entry_animation_duration * entryIndex; + } + public double InitialLifetimeOffset; - private Container progressBox; private RollingCounter noteCounter; + private JudgementRatioBox judgementRatio; + public string ObjectName = "Object"; public List HitEvents; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - float GoodCount = 0; - float GreatCount = 0; - float PerfectCount = 0; + Alpha = HitEvents.Any() ? 1 : 0.8f; + Colour = !HitEvents.Any() ? Color4.DarkGray : Color4.White; + bool allPerfect = HitEvents.Any() && HitEvents.All(h => h.Result == HitResult.Great); - foreach (var e in HitEvents) - { - switch (e.Result) - { - case HitResult.Great: - ++PerfectCount; - goto case HitResult.Good; - case HitResult.Good: - ++GreatCount; - goto case HitResult.Ok; - case HitResult.Ok: - ++GoodCount; - break; - } - } + Color4 textColour = Color4Extensions.FromHex("#66FFCC"); + Color4 boxColour = Color4Extensions.FromHex("#202624"); + Color4 borderColour = Color4Extensions.FromHex("#66FFCC"); + Color4 numberColour = Color4Extensions.FromHex("#66FFCC"); - Color4 textColour = !HitEvents.Any() ? Color4Extensions.FromHex("bcbcbc") : (PerfectCount == HitEvents.Count) ? Color4.White : Color4Extensions.FromHex("#3c5394"); - Color4 boxColour = !HitEvents.Any() ? Color4Extensions.FromHex("808080") : (PerfectCount == HitEvents.Count) ? Color4Extensions.FromHex("fda908") : Color4Extensions.FromHex("#DCE9F9"); - Color4 borderColour = !HitEvents.Any() ? Color4Extensions.FromHex("536277") : (PerfectCount == HitEvents.Count) ? Color4Extensions.FromHex("fda908") : Color4Extensions.FromHex("#98b8df"); - Color4 numberColour = (PerfectCount == HitEvents.Count && HitEvents.Any()) ? Color4.White : Color4Extensions.FromHex("#3c5394"); - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - RelativePositionAxes = Axes.Both; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(1, .16f); - Scale = new Vector2(1, 0); - Alpha = 0; - Masking = true; - BorderThickness = 2; BorderColour = borderColour; - CornerRadius = 5; - CornerExponent = 2.5f; - AlwaysPresent = true; InternalChildren = new Drawable[]{ new Box { RelativeSizeAxes = Axes.Both, Colour = boxColour, }, - new Container { // Left - RelativeSizeAxes = Axes.Both, - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Size = new Vector2(.33f, 1), - Child = new OsuSpriteText - { - Colour = textColour, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = ObjectName.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) - } - }, - progressBox = new Container { // Centre + new GridContainer{ RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(.34f, .8f), - CornerRadius = 5, - CornerExponent = 2.5f, - Masking = true, - BorderThickness = 2, - BorderColour = Color4.Black, - Children = new Drawable[]{ - new Box{ - RelativeSizeAxes = Axes.Both, - Colour = !HitEvents.Any() ? Color4Extensions.FromHex("343434"):Color4.DarkGray, + Content = new[]{ + new Drawable[]{ + new OsuSpriteText + { + Colour = textColour, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = ObjectName.ToUpper(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) + }, + judgementRatio = new JudgementRatioBox(HitEvents), + noteCounter = new TotalNoteCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = numberColour, + Current = { Value = 0 }, + } } } }, - new Container { // Right - RelativeSizeAxes = Axes.Both, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Size = new Vector2(.33f, 1), - Child = noteCounter = new TotalNoteCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = numberColour, - Current = { Value = 0 }, - } - }, }; - - progressBox.AddRange(new Drawable[]{ - new ChartBar(HitResult.Ok, GoodCount/HitEvents.Count){ - InitialLifetimeOffset = InitialLifetimeOffset - }, - new ChartBar(HitResult.Good, GreatCount/HitEvents.Count){ - InitialLifetimeOffset = InitialLifetimeOffset - }, - new ChartBar(HitResult.Great, PerfectCount/HitEvents.Count){ - InitialLifetimeOffset = InitialLifetimeOffset - }, - }); } protected override void LoadComplete() @@ -192,8 +149,9 @@ protected override void LoadComplete() { using (BeginDelayedSequence(InitialLifetimeOffset, true)) { - this.ScaleTo(1, entry_animation_duration, Easing.OutBack).FadeIn(); + this.ScaleTo(1, entry_animation_duration, Easing.OutBack); noteCounter.Current.Value = HitEvents.Count; + judgementRatio.AnimateEntry(); } }); } @@ -217,26 +175,91 @@ protected override OsuSpriteText CreateSpriteText() } } - private class ChartBar : Container + private class JudgementRatioBox : CompositeDrawable { - public double InitialLifetimeOffset; + private readonly Container ratioBoxes; - public ChartBar(HitResult result, float progress) + public JudgementRatioBox(List hitEvents) { RelativeSizeAxes = Axes.Both; - Size = new Vector2(float.IsNaN(progress) ? 0 : progress, 1); - Scale = new Vector2(0, 1); - Add(new Box + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + CornerRadius = 5; + CornerExponent = 2.5f; + Masking = true; + BorderThickness = 2; + BorderColour = Color4Extensions.FromHex("#66FFCC"); + Height = 0.8f; + + InternalChildren = new Drawable[]{ + new Box + { + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both + }, + ratioBoxes = new Container + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0,1), + } + }; + + if (hitEvents.Any()) { - RelativeSizeAxes = Axes.Both, - Colour = result.GetColorForSentakkiResult() - }); + void addRatioBox(HitResult result) + { + int resultCount = hitEvents.Count(h => h.Result >= result); + if (resultCount == 0) return; + + ratioBoxes.Add(new RatioBox + { + RelativeSizeAxes = Axes.Both, + Width = (float)resultCount / hitEvents.Count, + Colour = result.GetColorForSentakkiResult(), + Alpha = 0.8f + }); + }; + + addRatioBox(HitResult.Ok); + addRatioBox(HitResult.Good); + addRatioBox(HitResult.Great); + } } - protected override void LoadComplete() + public void AnimateEntry() => ratioBoxes.ScaleTo(1, bar_fill_duration, Easing.OutPow10); + + private class RatioBox : Sprite { - base.LoadComplete(); - this.Delay(InitialLifetimeOffset).ScaleTo(1, bar_fill_duration, Easing.OutPow10); + // Replace this shit with a shader ASAP + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + // Using a publically accessible texture + Texture = textures.Get("Icons/BeatmapDetails/accuracy", WrapMode.Repeat, WrapMode.Repeat); + Texture.SetData(generateTexture()); + RelativeSizeAxes = Axes.Both; + TextureRelativeSizeAxes = Axes.None; + TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); + } + + private TextureUpload generateTexture() + { + const int line_thickness = 4; + const int line_spacing = 6; + const int line_distance = line_thickness + line_spacing; + Image image = new Image(50, 50); + for (int y = 0; y < 50; ++y) + { + var span = image.GetPixelRowSpan(y); + for (int x = 0; x < 50; ++x) + { + bool pixelLit = ((x + y) % line_distance) <= line_thickness; + span[x] = new Rgba32(1f, 1f, 1f, pixelLit ? 1f : 0f); + } + } + return new TextureUpload(image); + } } } } From 6097803943506edba281d3a96e0f1e6830d91045 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 20 Jun 2021 21:23:53 +0200 Subject: [PATCH 02/12] Refactor JudgementChart --- osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs | 2 +- .../Statistics/ChartEntry.cs | 197 +++++++++++++ .../Statistics/JudgementChart.cs | 270 ++---------------- 3 files changed, 229 insertions(+), 240 deletions(-) create mode 100644 osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs diff --git a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs index d28d9e10a..b707143e0 100644 --- a/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs +++ b/osu.Game.Rulesets.Sentakki/SentakkiRuleset.cs @@ -142,7 +142,7 @@ public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatma { Columns = new[] { - new StatisticItem("Judgement Distribution", new JudgementChart(score.HitEvents.Where(e=>e.HitObject is SentakkiHitObject).ToList()) + new StatisticItem("Judgement Distribution", new JudgementChart(score.HitEvents) { RelativeSizeAxes = Axes.X, Size = new Vector2(1, 250) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs new file mode 100644 index 000000000..056c09cd9 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace osu.Game.Rulesets.Sentakki.Statistics +{ + internal class ChartEntry : CompositeDrawable + { + private static readonly Color4 accent_color = Color4Extensions.FromHex("#66FFCC"); + + private static readonly Color4 background_color = Color4Extensions.FromHex("#202624"); + + private const double bar_fill_duration = 3000; + + private readonly string name; + + private readonly IReadOnlyList hitEvents; + + private RollingCounter noteCounter; + + private Container ratioBoxes; + + public ChartEntry(string name, IReadOnlyList hitEvents) + { + this.name = name; + this.hitEvents = hitEvents; + } + + [BackgroundDependencyLoader] + private void load() + { + Anchor = Origin = Anchor.TopCentre; + RelativeSizeAxes = Axes.Both; + Height = 1f / 6f; + Scale = new Vector2(1, 0); + + Masking = true; + BorderThickness = 2; + BorderColour = accent_color; + CornerRadius = 5; + CornerExponent = 2.5f; + + Alpha = hitEvents.Any() ? 1 : 0.8f; + Colour = !hitEvents.Any() ? Color4.DarkGray : Color4.White; + + bool allPerfect = hitEvents.Any() && hitEvents.All(h => h.Result == HitResult.Great); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = background_color, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[]{ + new Drawable[]{ + new OsuSpriteText + { + Colour = accent_color, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = name.ToUpper(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 5, + CornerExponent = 2.5f, + Masking = true, + BorderThickness = 2, + BorderColour = accent_color, + Height = 0.8f, + Children = new Drawable[]{ + new Box + { + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both + }, + ratioBoxes = new Container + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0,1), + } + } + }, + noteCounter = new TotalNoteCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = accent_color, + Current = { Value = 0 }, + } + } + } + }, + }; + + if (!hitEvents.Any()) return; + + addRatioBoxFor(HitResult.Ok); + addRatioBoxFor(HitResult.Good); + addRatioBoxFor(HitResult.Great); + } + + public void AnimateEntry(double entryDuration) + { + this.ScaleTo(1, entryDuration, Easing.OutBack).TransformBindableTo(noteCounter.Current, hitEvents.Count); + ratioBoxes.ScaleTo(1, bar_fill_duration, Easing.OutPow10); + } + + + // This will add a box for each valid sentakki HitResult, excluding those that aren't visible + private void addRatioBoxFor(HitResult result) + { + int resultCount = hitEvents.Count(e => e.Result >= result); + + if (resultCount == 0) return; + + ratioBoxes.Add(new RatioBox + { + Width = (float)resultCount / hitEvents.Count, + Colour = result.GetColorForSentakkiResult(), + }); + } + + public class TotalNoteCounter : RollingCounter + { + protected override double RollingDuration => bar_fill_duration; + + protected override Easing RollingEasing => Easing.OutPow10; + + protected override string FormatCount(int count) => count.ToString("N0"); + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), + }; + } + + private class RatioBox : Sprite + { + // Replace this shit with a shader ASAP + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + // Using a publically accessible texture + Texture = textures.Get("Icons/BeatmapDetails/accuracy", WrapMode.Repeat, WrapMode.Repeat); + Texture.SetData(generateTexture()); + RelativeSizeAxes = Axes.Both; + TextureRelativeSizeAxes = Axes.None; + TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); + } + + private TextureUpload generateTexture() + { + const int line_thickness = 4; + const int line_spacing = 6; + const int line_distance = line_thickness + line_spacing; + Image image = new Image(50, 50); + for (int y = 0; y < 50; ++y) + { + var span = image.GetPixelRowSpan(y); + for (int x = 0; x < 50; ++x) + { + bool pixelLit = ((x + y) % line_distance) <= line_thickness; + span[x] = new Rgba32(1f, 1f, 1f, pixelLit ? 1f : 0f); + } + } + return new TextureUpload(image); + } + } + } +} diff --git a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs index 43ff88237..0aa1d5b82 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/JudgementChart.cs @@ -1,266 +1,58 @@ +using System; using System.Collections.Generic; using System.Linq; 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.OpenGL.Textures; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Sentakki.Objects; using osuTK; -using osuTK.Graphics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; namespace osu.Game.Rulesets.Sentakki.Statistics { public class JudgementChart : FillFlowContainer { private const double entry_animation_duration = 150; - private const double bar_fill_duration = 3000; - public JudgementChart(List hitEvents) + // The list of entries that we should create, placed here to reduce dupe code + private static readonly (string, Func)[] entries = { - Origin = Anchor.Centre; - Anchor = Anchor.Centre; - Size = new Vector2(500, 250); - //RelativeSizeAxes = Axes.Both; + ("Tap", e => e.HitObject is Tap x && !x.Break), + ("Hold", e => (e.HitObject is Hold x || e.HitObject is Hold.HoldHead) && !(e.HitObject as SentakkiLanedHitObject).Break), + ("Slide", e => e.HitObject is SlideBody x), + ("Touch", e => e.HitObject is Touch), + ("Touch Hold", e => e.HitObject is TouchHold), + ("Break", e => e.HitObject is SentakkiLanedHitObject x && x.Break), + }; - AddRangeInternal(new Drawable[]{ - new NoteEntry(0) - { - ObjectName = "Tap", - HitEvents = hitEvents.Where(e=> e.HitObject is Tap x && !x.Break).ToList(), - }, - new NoteEntry(1) - { - ObjectName = "Hold", - HitEvents = hitEvents.Where(e => (e.HitObject is Hold x || e.HitObject is Hold.HoldHead) && !(e.HitObject as SentakkiLanedHitObject).Break).ToList(), - }, - new NoteEntry(2) - { - ObjectName = "Slide", - HitEvents = hitEvents.Where(e => e.HitObject is SlideBody).ToList(), - }, - new NoteEntry(3) - { - ObjectName = "Touch", - HitEvents = hitEvents.Where(e => e.HitObject is Touch).ToList(), - }, - new NoteEntry(4) - { - ObjectName = "Touch Hold", - HitEvents = hitEvents.Where(e => e.HitObject is TouchHold).ToList(), - }, - new NoteEntry(5) - { - ObjectName = "Break", - HitEvents = hitEvents.Where(e => e.HitObject is SentakkiLanedHitObject x && x.Break).ToList(), - }, - }); - } + private readonly IReadOnlyList hitEvents; - private class NoteEntry : CompositeDrawable + public JudgementChart(IReadOnlyList hitEvents) { - public NoteEntry(int entryIndex) - { - AlwaysPresent = true; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - RelativeSizeAxes = Axes.Both; - Scale = new Vector2(1, 0); - - Masking = true; - BorderThickness = 2; - CornerRadius = 5; - CornerExponent = 2.5f; - - Height = 1f / 6f; - - InitialLifetimeOffset = entry_animation_duration * entryIndex; - } - - public double InitialLifetimeOffset; - private RollingCounter noteCounter; - - private JudgementRatioBox judgementRatio; - - public string ObjectName = "Object"; - public List HitEvents; - - [BackgroundDependencyLoader] - private void load() - { - Alpha = HitEvents.Any() ? 1 : 0.8f; - Colour = !HitEvents.Any() ? Color4.DarkGray : Color4.White; - bool allPerfect = HitEvents.Any() && HitEvents.All(h => h.Result == HitResult.Great); - - Color4 textColour = Color4Extensions.FromHex("#66FFCC"); - Color4 boxColour = Color4Extensions.FromHex("#202624"); - Color4 borderColour = Color4Extensions.FromHex("#66FFCC"); - Color4 numberColour = Color4Extensions.FromHex("#66FFCC"); - - BorderColour = borderColour; - - InternalChildren = new Drawable[]{ - new Box { - RelativeSizeAxes = Axes.Both, - Colour = boxColour, - }, - new GridContainer{ - RelativeSizeAxes = Axes.Both, - Content = new[]{ - new Drawable[]{ - new OsuSpriteText - { - Colour = textColour, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = ObjectName.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) - }, - judgementRatio = new JudgementRatioBox(HitEvents), - noteCounter = new TotalNoteCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = numberColour, - Current = { Value = 0 }, - } - } - } - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - ScheduleAfterChildren(() => - { - using (BeginDelayedSequence(InitialLifetimeOffset, true)) - { - this.ScaleTo(1, entry_animation_duration, Easing.OutBack); - noteCounter.Current.Value = HitEvents.Count; - judgementRatio.AnimateEntry(); - } - }); - } - - public class TotalNoteCounter : RollingCounter - { - protected override double RollingDuration => bar_fill_duration; + this.hitEvents = hitEvents; + } - protected override Easing RollingEasing => Easing.OutPow10; + [BackgroundDependencyLoader] + private void load() + { + Anchor = Origin = Anchor.Centre; + Size = new Vector2(1, 250); + RelativeSizeAxes = Axes.X; - protected override string FormatCount(long count) => count.ToString("N0"); + foreach (var (name, predicate) in entries) + AddInternal(new ChartEntry(name, hitEvents.Where(predicate).ToList())); + } - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), - }; - } - } + protected override void LoadComplete() + { + base.LoadComplete(); - private class JudgementRatioBox : CompositeDrawable + double delay = 0; + foreach (ChartEntry child in Children) { - private readonly Container ratioBoxes; - - public JudgementRatioBox(List hitEvents) - { - RelativeSizeAxes = Axes.Both; - Origin = Anchor.Centre; - Anchor = Anchor.Centre; - CornerRadius = 5; - CornerExponent = 2.5f; - Masking = true; - BorderThickness = 2; - BorderColour = Color4Extensions.FromHex("#66FFCC"); - Height = 0.8f; - - InternalChildren = new Drawable[]{ - new Box - { - Alpha = 0, - AlwaysPresent = true, - RelativeSizeAxes = Axes.Both - }, - ratioBoxes = new Container - { - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0,1), - } - }; - - if (hitEvents.Any()) - { - void addRatioBox(HitResult result) - { - int resultCount = hitEvents.Count(h => h.Result >= result); - if (resultCount == 0) return; - - ratioBoxes.Add(new RatioBox - { - RelativeSizeAxes = Axes.Both, - Width = (float)resultCount / hitEvents.Count, - Colour = result.GetColorForSentakkiResult(), - Alpha = 0.8f - }); - }; - - addRatioBox(HitResult.Ok); - addRatioBox(HitResult.Good); - addRatioBox(HitResult.Great); - } - } - - public void AnimateEntry() => ratioBoxes.ScaleTo(1, bar_fill_duration, Easing.OutPow10); - - private class RatioBox : Sprite - { - // Replace this shit with a shader ASAP - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - // Using a publically accessible texture - Texture = textures.Get("Icons/BeatmapDetails/accuracy", WrapMode.Repeat, WrapMode.Repeat); - Texture.SetData(generateTexture()); - RelativeSizeAxes = Axes.Both; - TextureRelativeSizeAxes = Axes.None; - TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); - } - - private TextureUpload generateTexture() - { - const int line_thickness = 4; - const int line_spacing = 6; - const int line_distance = line_thickness + line_spacing; - Image image = new Image(50, 50); - for (int y = 0; y < 50; ++y) - { - var span = image.GetPixelRowSpan(y); - for (int x = 0; x < 50; ++x) - { - bool pixelLit = ((x + y) % line_distance) <= line_thickness; - span[x] = new Rgba32(1f, 1f, 1f, pixelLit ? 1f : 0f); - } - } - return new TextureUpload(image); - } - } + using (BeginDelayedSequence(delay, true)) + child.AnimateEntry(entry_animation_duration); + delay += entry_animation_duration; } } } From 71e0188bffb2611a3d070e76fae196097f973e06 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 22 Jun 2021 16:01:41 +0200 Subject: [PATCH 03/12] Use shader to create diagonal line pattern --- .../Resources/Shaders/sh_DiagonalPattern.fs | 11 +++++ .../Shaders/sh_DiagonalPatternRounded.fs | 11 +++++ .../Statistics/ChartEntry.cs | 46 ++++++++----------- 3 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs create mode 100644 osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs diff --git a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs new file mode 100644 index 000000000..1f8e83cc6 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs @@ -0,0 +1,11 @@ +#include "sh_Utils.h" + +varying mediump vec2 v_TexCoord; +varying mediump vec4 v_TexRect; + +void main(void) +{ + float DistanceToLine = mod((v_TexCoord.x+v_TexCoord.y) / (v_TexRect[2] - v_TexRect[0]), 0.3); + bool pixelLit = DistanceToLine < 0.15; + gl_FragColor = vec4(1,1,1,pixelLit ? 1 : 0); +} diff --git a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs new file mode 100644 index 000000000..a26604563 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs @@ -0,0 +1,11 @@ +#include "sh_Utils.h" +#include "sh_Masking.h" + +varying mediump vec2 v_TexCoord; + +void main(void) +{ + float DistanceToLine = mod((v_TexCoord.x+v_TexCoord.y) / (v_TexRect[2] - v_TexRect[0]), 0.3); + bool pixelLit = DistanceToLine < 0.15; + gl_FragColor = getRoundedColor( vec4(1,1,1,pixelLit ? 1 : 0), v_TexCoord); +} diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 056c09cd9..aa5b8c866 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -14,8 +15,6 @@ using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; namespace osu.Game.Rulesets.Sentakki.Statistics { @@ -101,7 +100,7 @@ private void load() ratioBoxes = new Container { RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0,1), + Size = new Vector2(0,1), } } }, @@ -127,7 +126,7 @@ private void load() public void AnimateEntry(double entryDuration) { this.ScaleTo(1, entryDuration, Easing.OutBack).TransformBindableTo(noteCounter.Current, hitEvents.Count); - ratioBoxes.ScaleTo(1, bar_fill_duration, Easing.OutPow10); + ratioBoxes.ResizeWidthTo(1, bar_fill_duration, Easing.OutPow10); } @@ -140,8 +139,10 @@ private void addRatioBoxFor(HitResult result) ratioBoxes.Add(new RatioBox { + RelativeSizeAxes = Axes.Both, Width = (float)resultCount / hitEvents.Count, Colour = result.GetColorForSentakkiResult(), + Alpha = 0.8f }); } @@ -161,36 +162,29 @@ public class TotalNoteCounter : RollingCounter }; } - private class RatioBox : Sprite + private class RatioBox : Sprite, ITexturedShaderDrawable { - // Replace this shit with a shader ASAP + public new IShader TextureShader { get; private set; } + + public new IShader RoundedTextureShader { get; private set; } + + protected override DrawNode CreateDrawNode() => new RatioBoxDrawNode(this); + [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(ShaderManager shaders) { - // Using a publically accessible texture - Texture = textures.Get("Icons/BeatmapDetails/accuracy", WrapMode.Repeat, WrapMode.Repeat); - Texture.SetData(generateTexture()); - RelativeSizeAxes = Axes.Both; + Texture = Texture.WhitePixel.Crop(new Framework.Graphics.Primitives.RectangleF(0, 0, 1f, 1f), Axes.None, WrapMode.Repeat, WrapMode.Repeat); TextureRelativeSizeAxes = Axes.None; TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); + + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalPattern"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalPatternRounded"); } - private TextureUpload generateTexture() + private class RatioBoxDrawNode : SpriteDrawNode { - const int line_thickness = 4; - const int line_spacing = 6; - const int line_distance = line_thickness + line_spacing; - Image image = new Image(50, 50); - for (int y = 0; y < 50; ++y) - { - var span = image.GetPixelRowSpan(y); - for (int x = 0; x < 50; ++x) - { - bool pixelLit = ((x + y) % line_distance) <= line_thickness; - span[x] = new Rgba32(1f, 1f, 1f, pixelLit ? 1f : 0f); - } - } - return new TextureUpload(image); + public RatioBoxDrawNode(Sprite source) : base(source) { } + protected override bool CanDrawOpaqueInterior => false; } } } From 17b02a05f1dd452537387070662872f0866856c5 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 22 Jun 2021 20:03:38 +0200 Subject: [PATCH 04/12] Darken the line pattern a bit --- osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index aa5b8c866..5d20ff658 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -142,7 +142,7 @@ private void addRatioBoxFor(HitResult result) RelativeSizeAxes = Axes.Both, Width = (float)resultCount / hitEvents.Count, Colour = result.GetColorForSentakkiResult(), - Alpha = 0.8f + Alpha = 0.6f }); } From fe783d82eaef854a51db3f48ab7ab08d267122d7 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Tue, 22 Jun 2021 20:15:39 +0200 Subject: [PATCH 05/12] Rename shaders slightly --- .../{sh_DiagonalPattern.fs => sh_DiagonalLinePattern.fs} | 0 ...onalPatternRounded.fs => sh_DiagonalLinePatternRounded.fs} | 0 osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Sentakki/Resources/Shaders/{sh_DiagonalPattern.fs => sh_DiagonalLinePattern.fs} (100%) rename osu.Game.Rulesets.Sentakki/Resources/Shaders/{sh_DiagonalPatternRounded.fs => sh_DiagonalLinePatternRounded.fs} (100%) diff --git a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs similarity index 100% rename from osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPattern.fs rename to osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs diff --git a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePatternRounded.fs similarity index 100% rename from osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalPatternRounded.fs rename to osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePatternRounded.fs diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 5d20ff658..383e7686d 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -177,8 +177,8 @@ private void load(ShaderManager shaders) TextureRelativeSizeAxes = Axes.None; TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalPattern"); - RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalPatternRounded"); + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalLinePattern"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalLinePatternRounded"); } private class RatioBoxDrawNode : SpriteDrawNode From 22f49daab48c356443c9a53056e1d87299c36902 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 14:39:47 +0200 Subject: [PATCH 06/12] Add fallback shader to chart entries --- osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index aa2636d4d..8f0e97b9e 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -144,7 +145,7 @@ private void addRatioBoxFor(HitResult result) RelativeSizeAxes = Axes.Both, Width = (float)resultCount / hitEvents.Count, Colour = result.GetColorForSentakkiResult(), - Alpha = 0.6f + Alpha = 1f }); } @@ -177,7 +178,14 @@ private void load(ShaderManager shaders, IRenderer renderer) TextureRelativeSizeAxes = Axes.None; TextureRectangle = new Framework.Graphics.Primitives.RectangleF(0, 0, 50, 50); - TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalLinePattern"); + try + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "DiagonalLinePattern"); + } + catch // Fallback + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + } } private class RatioBoxDrawNode : SpriteDrawNode From d5a3e0247b55fc9f8023488548c6bd31351631b6 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 14:56:01 +0200 Subject: [PATCH 07/12] Highlight all perfect rows --- osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 8f0e97b9e..00ead9124 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -62,12 +63,17 @@ private void load() bool allPerfect = hitEvents.Any() && hitEvents.All(h => h.Result == HitResult.Great); + var bg = background_color; + + if (allPerfect) + bg = Interpolation.ValueAt(0.1, bg, accent_color, 0, 1); + InternalChildren = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = background_color, + Colour = bg, }, new GridContainer { From 65378fed5897f32bb24161cab530ecad3a423a3a Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 17:27:54 +0200 Subject: [PATCH 08/12] Show full breakdown for each note --- .../Statistics/ChartEntry.cs | 113 ++++++++++++++++-- 1 file changed, 100 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 00ead9124..549e09c73 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -1,11 +1,10 @@ -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Rendering; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; @@ -34,8 +33,6 @@ internal partial class ChartEntry : CompositeDrawable private readonly IReadOnlyList hitEvents; - private RollingCounter noteCounter = null!; - private Container ratioBoxes = null!; public ChartEntry(string name, IReadOnlyList hitEvents) @@ -78,10 +75,16 @@ private void load() new GridContainer { RelativeSizeAxes = Axes.Both, + ColumnDimensions = new Dimension[]{ + new Dimension(GridSizeMode.Relative, 0.3f), + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize) + }, Content = new[]{ new Drawable[]{ - new OsuSpriteText + new SpriteText { + Margin = new MarginPadding{ Horizontal = 30 }, Colour = accent_color, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -90,6 +93,7 @@ private void load() }, new Container { + Margin = new MarginPadding{ Horizontal = 10 }, RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -113,12 +117,8 @@ private void load() } } }, - noteCounter = new TotalNoteCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = accent_color, - Current = { Value = 0 }, + new JudgementBreakdowns(hitEvents){ + Margin = new MarginPadding{ Horizontal = 30 }, } } } @@ -134,7 +134,7 @@ private void load() public void AnimateEntry(double entryDuration) { - this.ScaleTo(1, entryDuration, Easing.OutBack).TransformBindableTo(noteCounter.Current, hitEvents.Count); + this.ScaleTo(1, entryDuration, Easing.OutBack); ratioBoxes.ResizeWidthTo(1, bar_fill_duration, Easing.OutPow10); } @@ -151,10 +151,83 @@ private void addRatioBoxFor(HitResult result) RelativeSizeAxes = Axes.Both, Width = (float)resultCount / hitEvents.Count, Colour = result.GetColorForSentakkiResult(), - Alpha = 1f + Alpha = .8f }); } + public partial class JudgementBreakdowns : FillFlowContainer + { + private static readonly HitResult[] valid_results = new HitResult[]{ + HitResult.Great, + HitResult.Good, + HitResult.Ok, + HitResult.Miss + }; + + public JudgementBreakdowns(IReadOnlyList hitEvents) + { + Anchor = Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(10f, 0f); + Direction = FillDirection.Horizontal; + + AddRangeInternal( + new Drawable[]{ + new ResultsCounter("Total", hitEvents.Count){Colour = accent_color}, + new Box { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(2, 1), + Colour = Color4.Gray, + Alpha = 0.8f, + Margin = new MarginPadding{ Horizontal = 10 } + } + } + ); + + foreach (var resultType in valid_results) + { + int amount = hitEvents.Count(e => e.Result == resultType); + var colour = resultType.GetColorForSentakkiResult(); + var hspa = new HSPAColour(colour) { P = 0.6f }.ToColor4(); + AddInternal(new ResultsCounter(resultType.GetDisplayNameForSentakkiResult(), amount) + { + Colour = Interpolation.ValueAt(0.5f, colour, hspa, 0, 1), + Scale = new Vector2(0.8f) + }); + } + } + + private partial class ResultsCounter : FillFlowContainer + { + public ResultsCounter(string title, int count) + { + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(0, -5); + Direction = FillDirection.Vertical; + Anchor = Origin = Anchor.Centre; + + AddRangeInternal(new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = title.ToUpper(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) + }, + new TotalNoteCounter(count){ + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + } + } + + } + public partial class TotalNoteCounter : RollingCounter { protected override double RollingDuration => bar_fill_duration; @@ -163,12 +236,26 @@ public partial class TotalNoteCounter : RollingCounter protected override LocalisableString FormatCount(int count) => count.ToString("N0"); + private int totalValue; + + public TotalNoteCounter(int value) + { + Current = new Bindable { Value = 0 }; + totalValue = value; + } + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold), }; + + protected override void LoadComplete() + { + base.LoadComplete(); + this.TransformBindableTo(Current, totalValue); + } } private partial class RatioBox : Sprite, ITexturedShaderDrawable From e2e2fbbcbc0e3c45f013b495b31d4807d17af240 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 18:06:33 +0200 Subject: [PATCH 09/12] Show detailed statistics onHover --- .../Statistics/ChartEntry.cs | 102 ++++++++++++------ 1 file changed, 67 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 549e09c73..f1c348eba 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Graphics; @@ -41,6 +42,9 @@ public ChartEntry(string name, IReadOnlyList hitEvents) this.hitEvents = hitEvents; } + private Drawable simpleStats = null!; + private Drawable detailedStats = null!; + [BackgroundDependencyLoader] private void load() { @@ -78,13 +82,11 @@ private void load() ColumnDimensions = new Dimension[]{ new Dimension(GridSizeMode.Relative, 0.3f), new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.AutoSize) }, Content = new[]{ new Drawable[]{ new SpriteText { - Margin = new MarginPadding{ Horizontal = 30 }, Colour = accent_color, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,33 +95,49 @@ private void load() }, new Container { - Margin = new MarginPadding{ Horizontal = 10 }, RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, Anchor = Anchor.Centre, - CornerRadius = 5, - CornerExponent = 2.5f, - Masking = true, - BorderThickness = 2, - BorderColour = accent_color, - Height = 0.8f, - Children = new Drawable[]{ - new Box - { - Alpha = 0, - AlwaysPresent = true, - RelativeSizeAxes = Axes.Both - }, - ratioBoxes = new Container + Origin = Anchor.Centre, + + Children = new Drawable[] + { + simpleStats = new GridContainer { RelativeSizeAxes = Axes.Both, - Size = new Vector2(0,1), - } + Content = new[]{ + new Drawable[]{ + new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 5, + CornerExponent = 2.5f, + Masking = true, + BorderThickness = 2, + BorderColour = accent_color, + Height = 0.8f, + Children = new Drawable[]{ + new Box + { + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both + }, + ratioBoxes = new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0,1), + } + } + }, + new ResultsCounter("Total", hitEvents.Count){ Colour = accent_color }, + } + } + }, + detailedStats = new JudgementBreakdowns(hitEvents) { Alpha = 0 } } }, - new JudgementBreakdowns(hitEvents){ - Margin = new MarginPadding{ Horizontal = 30 }, - } } } }, @@ -132,6 +150,21 @@ private void load() addRatioBoxFor(HitResult.Great); } + + protected override bool OnHover(HoverEvent e) + { + simpleStats.FadeOut(100); + detailedStats.FadeIn(100); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + detailedStats.FadeOut(100); + simpleStats.FadeIn(100); + } + + public void AnimateEntry(double entryDuration) { this.ScaleTo(1, entryDuration, Easing.OutBack); @@ -199,18 +232,19 @@ public JudgementBreakdowns(IReadOnlyList hitEvents) }); } } + } - private partial class ResultsCounter : FillFlowContainer + private partial class ResultsCounter : FillFlowContainer + { + public ResultsCounter(string title, int count) { - public ResultsCounter(string title, int count) - { - AutoSizeAxes = Axes.Both; - Spacing = new Vector2(0, -5); - Direction = FillDirection.Vertical; - Anchor = Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(0, -5); + Direction = FillDirection.Vertical; + Anchor = Origin = Anchor.Centre; - AddRangeInternal(new Drawable[] - { + AddRangeInternal(new Drawable[] + { new OsuSpriteText { Anchor = Anchor.Centre, @@ -222,10 +256,8 @@ public ResultsCounter(string title, int count) Anchor = Anchor.Centre, Origin = Anchor.Centre, } - }); - } + }); } - } public partial class TotalNoteCounter : RollingCounter From d0d8465ea70d622cca6b21fc5096868818842a4e Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 18:06:42 +0200 Subject: [PATCH 10/12] Adjust diagonal line look --- .../Resources/Shaders/sh_DiagonalLinePattern.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs index 0be777bd7..fcc7956c1 100644 --- a/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs +++ b/osu.Game.Rulesets.Sentakki/Resources/Shaders/sh_DiagonalLinePattern.fs @@ -8,5 +8,5 @@ void main(void) { float DistanceToLine = mod((v_TexCoord.x+v_TexCoord.y) / (v_TexRect[2] - v_TexRect[0]), 0.3); bool pixelLit = DistanceToLine < 0.15; - o_Colour = getRoundedColor( vec4(1,1,1,pixelLit ? 1 : 0), v_TexCoord); + o_Colour = getRoundedColor( pixelLit ? vec4(1,1,1,1) : vec4(0.5,0.5,0.5,0.8), v_TexCoord); } From 4e394ac7cf94969218cc81894c65b0877cb8ad62 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 13 Aug 2023 18:25:13 +0200 Subject: [PATCH 11/12] Minor cleanups to ChartEntry --- .../Statistics/ChartEntry.cs | 130 ++++++++++-------- 1 file changed, 69 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index f1c348eba..7ce143a6a 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -33,16 +33,13 @@ internal partial class ChartEntry : CompositeDrawable private readonly string name; private readonly IReadOnlyList hitEvents; - - private Container ratioBoxes = null!; - public ChartEntry(string name, IReadOnlyList hitEvents) { this.name = name; this.hitEvents = hitEvents; } - private Drawable simpleStats = null!; + private SimpleStatsSegment simpleStats = null!; private Drawable detailedStats = null!; [BackgroundDependencyLoader] @@ -98,59 +95,18 @@ private void load() RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] { - simpleStats = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[]{ - new Drawable[]{ - new Container - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - CornerRadius = 5, - CornerExponent = 2.5f, - Masking = true, - BorderThickness = 2, - BorderColour = accent_color, - Height = 0.8f, - Children = new Drawable[]{ - new Box - { - Alpha = 0, - AlwaysPresent = true, - RelativeSizeAxes = Axes.Both - }, - ratioBoxes = new Container - { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0,1), - } - } - }, - new ResultsCounter("Total", hitEvents.Count){ Colour = accent_color }, - } - } - }, - detailedStats = new JudgementBreakdowns(hitEvents) { Alpha = 0 } + simpleStats = new SimpleStatsSegment(hitEvents), + detailedStats = new DetailedStatsSegment(hitEvents) { Alpha = 0 } } }, } } }, }; - - if (!hitEvents.Any()) return; - - addRatioBoxFor(HitResult.Ok); - addRatioBoxFor(HitResult.Good); - addRatioBoxFor(HitResult.Great); } - protected override bool OnHover(HoverEvent e) { simpleStats.FadeOut(100); @@ -168,27 +124,79 @@ protected override void OnHoverLost(HoverLostEvent e) public void AnimateEntry(double entryDuration) { this.ScaleTo(1, entryDuration, Easing.OutBack); - ratioBoxes.ResizeWidthTo(1, bar_fill_duration, Easing.OutPow10); + simpleStats.AnimateEntry(); } - - // This will add a box for each valid sentakki HitResult, excluding those that aren't visible - private void addRatioBoxFor(HitResult result) + private partial class SimpleStatsSegment : GridContainer { - int resultCount = hitEvents.Count(e => e.Result >= result); + private Container ratioBoxes; + private IReadOnlyList hitEvents; - if (resultCount == 0) return; + public SimpleStatsSegment(IReadOnlyList hitEvents) + { + this.hitEvents = hitEvents; + RelativeSizeAxes = Axes.Both; + Content = new[]{ + new Drawable[]{ + new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + CornerRadius = 5, + CornerExponent = 2.5f, + Masking = true, + BorderThickness = 2, + BorderColour = accent_color, + Height = 0.8f, + Children = new Drawable[]{ + new Box + { + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both + }, + ratioBoxes = new Container + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0,1), + } + } + }, + new ResultsCounter("Total", hitEvents.Count){ Colour = accent_color }, + } + }; + + if (!hitEvents.Any()) return; + + addRatioBoxFor(HitResult.Ok); + addRatioBoxFor(HitResult.Good); + addRatioBoxFor(HitResult.Great); + } + + public void AnimateEntry() + { + ratioBoxes.ResizeWidthTo(1, bar_fill_duration, Easing.OutPow10); + } - ratioBoxes.Add(new RatioBox + // This will add a box for each valid sentakki HitResult, excluding those that aren't visible + private void addRatioBoxFor(HitResult result) { - RelativeSizeAxes = Axes.Both, - Width = (float)resultCount / hitEvents.Count, - Colour = result.GetColorForSentakkiResult(), - Alpha = .8f - }); + int resultCount = hitEvents.Count(e => e.Result >= result); + + if (resultCount == 0) return; + + ratioBoxes.Add(new RatioBox + { + RelativeSizeAxes = Axes.Both, + Width = (float)resultCount / hitEvents.Count, + Colour = result.GetColorForSentakkiResult(), + Alpha = .8f + }); + } } - public partial class JudgementBreakdowns : FillFlowContainer + public partial class DetailedStatsSegment : FillFlowContainer { private static readonly HitResult[] valid_results = new HitResult[]{ HitResult.Great, @@ -197,7 +205,7 @@ public partial class JudgementBreakdowns : FillFlowContainer HitResult.Miss }; - public JudgementBreakdowns(IReadOnlyList hitEvents) + public DetailedStatsSegment(IReadOnlyList hitEvents) { Anchor = Origin = Anchor.Centre; From e3dae63f2f335c0b483425b962e8be13ccf367bd Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 14 Aug 2023 20:08:50 +0200 Subject: [PATCH 12/12] Adjust how the detailed counters appear They now slide in from the right, squishing the bars --- .../Statistics/ChartEntry.cs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs index 7ce143a6a..2940e236a 100644 --- a/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs +++ b/osu.Game.Rulesets.Sentakki/Statistics/ChartEntry.cs @@ -77,30 +77,44 @@ private void load() { RelativeSizeAxes = Axes.Both, ColumnDimensions = new Dimension[]{ - new Dimension(GridSizeMode.Relative, 0.3f), - new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.Distributed, minSize: 10, maxSize:30), + new Dimension(GridSizeMode.AutoSize), // Left text + new Dimension(GridSizeMode.Distributed, minSize: 5, maxSize: 30), + new Dimension(GridSizeMode.Distributed), // Bars + new Dimension(GridSizeMode.Distributed, minSize: 5, maxSize: 30), + new Dimension(GridSizeMode.AutoSize), // Total count + new Dimension(GridSizeMode.AutoSize), // Detailed count + new Dimension(GridSizeMode.Distributed, minSize: 10, maxSize:30), }, Content = new[]{ new Drawable[]{ - new SpriteText - { - Colour = accent_color, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = name.ToUpper(), - Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold) - }, + null!, new Container { - RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] + Size = new Vector2(102,20), + Child = new OsuSpriteText { - simpleStats = new SimpleStatsSegment(hitEvents), - detailedStats = new DetailedStatsSegment(hitEvents) { Alpha = 0 } - } + Colour = accent_color, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = name.ToUpper(), + Font = OsuFont.Torus.With(size: 20, weight: FontWeight.Bold), + }, + }, + null!, // Container only + simpleStats = new SimpleStatsSegment(hitEvents), + null!, + new ResultsCounter("Total", hitEvents.Count) + { + Colour = accent_color , }, + detailedStats = new DetailedStatsSegment(hitEvents) { + Scale = new Vector2(0,1), + Margin = new MarginPadding{ Left = 15 } + }, + null!, } } }, @@ -109,15 +123,13 @@ private void load() protected override bool OnHover(HoverEvent e) { - simpleStats.FadeOut(100); - detailedStats.FadeIn(100); + detailedStats.ScaleTo(Vector2.One, 200, Easing.OutElasticQuarter); return true; } protected override void OnHoverLost(HoverLostEvent e) { - detailedStats.FadeOut(100); - simpleStats.FadeIn(100); + detailedStats.ScaleTo(new Vector2(0, 1), 200, Easing.OutExpo); } @@ -163,7 +175,6 @@ public SimpleStatsSegment(IReadOnlyList hitEvents) } } }, - new ResultsCounter("Total", hitEvents.Count){ Colour = accent_color }, } }; @@ -213,21 +224,6 @@ public DetailedStatsSegment(IReadOnlyList hitEvents) Spacing = new Vector2(10f, 0f); Direction = FillDirection.Horizontal; - AddRangeInternal( - new Drawable[]{ - new ResultsCounter("Total", hitEvents.Count){Colour = accent_color}, - new Box { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(2, 1), - Colour = Color4.Gray, - Alpha = 0.8f, - Margin = new MarginPadding{ Horizontal = 10 } - } - } - ); - foreach (var resultType in valid_results) { int amount = hitEvents.Count(e => e.Result == resultType);