// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. 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.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation { private readonly EffectControlPoint effect; private Bindable kiai = null!; [Resolved] private EditorBeatmap beatmap { get; set; } = null!; [Resolved] private OsuColour colours { get; set; } = null!; public EffectPointVisualisation(EffectControlPoint point) { RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Y; effect = point; } [BackgroundDependencyLoader] private void load() { kiai = effect.KiaiModeBindable.GetBoundCopy(); kiai.BindValueChanged(_ => refreshDisplay(), true); } private EffectControlPoint? nextControlPoint; protected override void LoadComplete() { base.LoadComplete(); // Due to the limitations of ControlPointInfo, it's impossible to know via event flow when the next kiai point has changed. // This is due to the fact that an EffectPoint can be added to an existing group. We would need to bind to ItemAdded on *every* // future group to track this. // // I foresee this being a potential performance issue on beatmaps with many control points, so let's limit how often we check // for changes. ControlPointInfo needs a refactor to make this flow better, but it should do for now. Scheduler.AddDelayed(() => { var next = beatmap.ControlPointInfo.EffectPoints.FirstOrDefault(c => c.Time > effect.Time); if (!ReferenceEquals(nextControlPoint, next)) { nextControlPoint = next; refreshDisplay(); } }, 100, true); } private void refreshDisplay() { ClearInternal(); AddInternal(new ControlPointVisualisation(effect)); if (!kiai.Value) return; // handle kiai duration // eventually this will be simpler when we have control points with durations. if (nextControlPoint != null) { RelativeSizeAxes = Axes.Both; Origin = Anchor.TopLeft; Width = (float)(nextControlPoint.Time - effect.Time); AddInternal(new PointVisualisation { RelativeSizeAxes = Axes.Both, Origin = Anchor.TopLeft, Width = 1, Height = 0.25f, Depth = float.MaxValue, Colour = effect.GetRepresentingColour(colours).Darken(0.5f), }); } } // kiai sections display duration, so are required to be visualised. public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; } }