diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 3c87b166ac..e9a20761b3 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -109,15 +109,11 @@ namespace osu.Game.Graphics.UserInterface } } - [Flags] public enum BarDirection { - LeftToRight = 1, - RightToLeft = 1 << 1, - TopToBottom = 1 << 2, - BottomToTop = 1 << 3, - - Vertical = TopToBottom | BottomToTop, - Horizontal = LeftToRight | RightToLeft, + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop } } diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 2e9fd6734f..45eeba11d5 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -5,15 +5,23 @@ using osuTK; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; +using System; namespace osu.Game.Graphics.UserInterface { - public class BarGraph : FillFlowContainer + public class BarGraph : Drawable { + private const int resize_duration = 250; + private const Easing easing = Easing.InOutCubic; + /// /// Manually sets the max value, if null is instead used /// @@ -21,22 +29,21 @@ namespace osu.Game.Graphics.UserInterface private BarDirection direction = BarDirection.BottomToTop; - public new BarDirection Direction + public BarDirection Direction { get => direction; set { - direction = value; - base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + if (direction == value) + return; - foreach (var bar in Children) - { - bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); - bar.Direction = direction; - } + direction = value; + Invalidate(Invalidation.DrawNode); } } + private IEnumerable bars; + /// /// A list of floats that defines the length of each /// @@ -44,38 +51,188 @@ namespace osu.Game.Graphics.UserInterface { set { - List bars = Children.ToList(); + List newBars = bars?.ToList() ?? new List(); - foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + int newCount = value.Count(); + + float size = newCount; + if (size != 0) + size = 1.0f / size; + + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = newBars.Count > index ? newBars[index] : null })) { float length = MaxValue ?? value.Max(); if (length != 0) - length = bar.Value / length; - - float size = value.Count(); - if (size != 0) - size = 1.0f / size; + length = Math.Max(0f, bar.Value / length); if (bar.Bar != null) { - bar.Bar.Length = length; - bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1); + bar.Bar.OldValue = bar.Bar.Value; + + bar.Bar.Value = length; + bar.Bar.ShortSide = size; } else { - Add(new Bar + newBars.Add(new BarDescriptor { - RelativeSizeAxes = Axes.Both, - Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1), - Length = length, - Direction = Direction, + Value = length, + ShortSide = size }); } } - //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList(), true); + if (newBars.Count > newCount) + newBars.RemoveRange(newCount, newBars.Count - newCount); + + bars = newBars; + + animationStartTime = Clock.CurrentTime; + animationComplete = false; } } + + private double animationStartTime; + private bool animationComplete; + + private IShader shader = null!; + private Texture texture = null!; + + [BackgroundDependencyLoader] + private void load(IRenderer renderer, ShaderManager shaders) + { + texture = renderer.WhitePixel; + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + } + + protected override void Update() + { + base.Update(); + + if (noBars) + return; + + double currentTime = Clock.CurrentTime; + + if (currentTime < animationStartTime + resize_duration) + { + foreach (var bar in bars) + bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); + + Invalidate(Invalidation.DrawNode); + return; + } + else if (!animationComplete) + { + foreach (var bar in bars) + bar.IntermediateValue = bar.Value; + + Invalidate(Invalidation.DrawNode); + + animationComplete = true; + return; + } + } + + private bool noBars => bars?.Any() != true; + + protected override DrawNode CreateDrawNode() => new BarGraphDrawNode(this); + + private class BarGraphDrawNode : DrawNode + { + public new BarGraph Source => (BarGraph)base.Source; + + public BarGraphDrawNode(BarGraph source) + : base(source) + { + } + + private IShader shader = null!; + private Texture texture = null!; + private Vector2 drawSize; + private BarDirection direction; + + private readonly List bars = new List(); + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + drawSize = Source.DrawSize; + direction = Source.direction; + + bars.Clear(); + + if (Source.noBars) + return; + + bars.AddRange(Source.bars); + } + + public override void Draw(IRenderer renderer) + { + base.Draw(renderer); + + if (!bars.Any()) + return; + + shader.Bind(); + + for (int i = 0; i < bars.Count; i++) + { + var bar = bars[i]; + + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.IntermediateValue : bar.ShortSide); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.IntermediateValue : bar.ShortSide); + + Vector2 topLeft; + + switch (direction) + { + default: + case BarDirection.LeftToRight: + topLeft = new Vector2(0, i * barHeight); + break; + + case BarDirection.RightToLeft: + topLeft = new Vector2(drawSize.X - barWidth, i * barHeight); + break; + + case BarDirection.TopToBottom: + topLeft = new Vector2(i * barWidth, 0); + break; + + case BarDirection.BottomToTop: + topLeft = new Vector2(i * barWidth, drawSize.Y - barHeight); + break; + } + + Vector2 topRight = topLeft + new Vector2(barWidth, 0); + Vector2 bottomLeft = topLeft + new Vector2(0, barHeight); + Vector2 bottomRight = bottomLeft + new Vector2(barWidth, 0); + + var drawQuad = new Quad( + Vector2Extensions.Transform(topLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(topRight, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix) + ); + + renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour); + } + + shader.Unbind(); + } + } + + private class BarDescriptor + { + public float OldValue { get; set; } + public float Value { get; set; } + public float IntermediateValue { get; set; } + public float ShortSide { get; set; } + } } }