// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Textures; using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class SliderBody : Container, ISliderProgress { private readonly Path path; private readonly BufferedContainer container; public float PathWidth { get { return path.PathWidth; } set { path.PathWidth = value; } } public readonly Bindable SnakingIn = new Bindable(); public readonly Bindable SnakingOut = new Bindable(); public double? SnakedStart { get; private set; } public double? SnakedEnd { get; private set; } private Color4 accentColour; /// /// Used to colour the path. /// public Color4 AccentColour { get { return accentColour; } set { if (accentColour == value) return; accentColour = value; if (LoadState == LoadState.Ready) Schedule(reloadTexture); } } private Color4 borderColour = Color4.White; /// /// Used to colour the path border. /// public new Color4 BorderColour { get { return borderColour; } set { if (borderColour == value) return; borderColour = value; if (LoadState == LoadState.Ready) Schedule(reloadTexture); } } public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; private int textureWidth => (int)PathWidth * 2; private readonly Slider slider; public SliderBody(Slider s) { slider = s; Children = new Drawable[] { container = new BufferedContainer { CacheDrawnFrameBuffer = true, Children = new Drawable[] { path = new Path { Blending = BlendingMode.None, }, } }, }; container.Attach(RenderbufferInternalFormat.DepthComponent16); } public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos); public void SetRange(double p0, double p1) { if (p0 > p1) MathHelper.Swap(ref p0, ref p1); if (updateSnaking(p0, p1)) { // Autosizing does not give us the desired behaviour here. // We want the container to have the same size as the slider, // and to be positioned such that the slider head is at (0,0). container.Size = path.Size; container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - CurrentCurve[0]); container.ForceRedraw(); } } [BackgroundDependencyLoader] private void load() { reloadTexture(); } private void reloadTexture() { var texture = new Texture(textureWidth, 1); //initialise background var upload = new TextureUpload(textureWidth * 4); var bytes = upload.Data; const float aa_portion = 0.02f; const float border_portion = 0.128f; const float gradient_portion = 1 - border_portion; const float opacity_at_centre = 0.3f; const float opacity_at_edge = 0.8f; for (int i = 0; i < textureWidth; i++) { float progress = (float)i / (textureWidth - 1); if (progress <= border_portion) { bytes[i * 4] = (byte)(BorderColour.R * 255); bytes[i * 4 + 1] = (byte)(BorderColour.G * 255); bytes[i * 4 + 2] = (byte)(BorderColour.B * 255); bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255)); } else { progress -= border_portion; bytes[i * 4] = (byte)(AccentColour.R * 255); bytes[i * 4 + 1] = (byte)(AccentColour.G * 255); bytes[i * 4 + 2] = (byte)(AccentColour.B * 255); bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (AccentColour.A * 255)); } } texture.SetData(upload); path.Texture = texture; } public readonly List CurrentCurve = new List(); private bool updateSnaking(double p0, double p1) { if (SnakedStart == p0 && SnakedEnd == p1) return false; SnakedStart = p0; SnakedEnd = p1; slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); path.ClearVertices(); foreach (Vector2 p in CurrentCurve) path.AddVertex(p - CurrentCurve[0]); return true; } public void UpdateProgress(double progress, int span) { double start = 0; double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; if (span >= slider.SpanCount() - 1) { if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) { start = 0; end = SnakingOut ? progress : 1; } else { start = SnakingOut ? progress : 0; } } SetRange(start, end); } } }