2020-01-27 16:34:25 +08:00
|
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
|
|
2020-10-09 14:38:58 +08:00
|
|
|
|
using System;
|
2022-09-27 16:11:36 +08:00
|
|
|
|
using System.Diagnostics;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
|
using osu.Framework.Bindables;
|
2020-10-09 14:57:31 +08:00
|
|
|
|
using osu.Framework.Caching;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
using osu.Framework.Graphics;
|
2024-03-16 20:20:37 +08:00
|
|
|
|
using osu.Framework.Graphics.Primitives;
|
2022-09-27 16:11:36 +08:00
|
|
|
|
using osu.Framework.Logging;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
using osu.Game.Beatmaps;
|
|
|
|
|
using osu.Game.Graphics;
|
|
|
|
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
|
|
|
|
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
|
2022-05-24 16:02:19 +08:00
|
|
|
|
using osuTK;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|
|
|
|
{
|
2020-10-09 14:38:58 +08:00
|
|
|
|
public partial class TimelineTickDisplay : TimelinePart<PointVisualisation>
|
2020-01-27 16:34:25 +08:00
|
|
|
|
{
|
2024-03-16 20:20:37 +08:00
|
|
|
|
// With current implementation every tick in the sub-tree should be visible, no need to check whether they are masked away.
|
|
|
|
|
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
|
|
|
|
|
2020-01-27 16:34:25 +08:00
|
|
|
|
[Resolved]
|
|
|
|
|
private EditorBeatmap beatmap { get; set; } = null!;
|
|
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
|
private Bindable<WorkingBeatmap> working { get; set; } = null!;
|
|
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
|
private BindableBeatDivisor beatDivisor { get; set; } = null!;
|
|
|
|
|
|
2020-12-02 03:08:31 +08:00
|
|
|
|
[Resolved]
|
2020-12-01 15:44:08 +08:00
|
|
|
|
private IEditorChangeHandler? changeHandler { get; set; }
|
|
|
|
|
|
2020-01-27 16:34:25 +08:00
|
|
|
|
[Resolved]
|
|
|
|
|
private OsuColour colours { get; set; } = null!;
|
|
|
|
|
|
2020-01-27 17:07:46 +08:00
|
|
|
|
public TimelineTickDisplay()
|
2020-01-27 16:34:25 +08:00
|
|
|
|
{
|
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 14:57:31 +08:00
|
|
|
|
private readonly Cached tickCache = new Cached();
|
|
|
|
|
|
|
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
|
private void load()
|
|
|
|
|
{
|
2020-12-01 15:44:08 +08:00
|
|
|
|
beatDivisor.BindValueChanged(_ => invalidateTicks());
|
|
|
|
|
|
2020-12-02 03:08:31 +08:00
|
|
|
|
if (changeHandler != null)
|
|
|
|
|
// currently this is the best way to handle any kind of timing changes.
|
|
|
|
|
changeHandler.OnStateChange += invalidateTicks;
|
2020-12-01 15:44:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void invalidateTicks()
|
|
|
|
|
{
|
|
|
|
|
tickCache.Invalidate();
|
2020-10-09 14:57:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 15:46:54 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The visible time/position range of the timeline.
|
|
|
|
|
/// </summary>
|
2020-10-09 14:57:31 +08:00
|
|
|
|
private (float min, float max) visibleRange = (float.MinValue, float.MaxValue);
|
|
|
|
|
|
2020-10-09 15:46:54 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The next time/position value to the left of the display when tick regeneration needs to be run.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private float? nextMinTick;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The next time/position value to the right of the display when tick regeneration needs to be run.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private float? nextMaxTick;
|
|
|
|
|
|
2020-10-09 14:38:58 +08:00
|
|
|
|
[Resolved]
|
|
|
|
|
private Timeline? timeline { get; set; }
|
2020-01-27 16:34:25 +08:00
|
|
|
|
|
2020-10-09 14:38:58 +08:00
|
|
|
|
protected override void Update()
|
2020-01-27 16:34:25 +08:00
|
|
|
|
{
|
2020-10-09 14:38:58 +08:00
|
|
|
|
base.Update();
|
|
|
|
|
|
2022-09-27 19:09:21 +08:00
|
|
|
|
if (timeline == null || DrawWidth <= 0) return;
|
2022-09-27 16:18:45 +08:00
|
|
|
|
|
|
|
|
|
(float, float) newRange = (
|
|
|
|
|
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X,
|
|
|
|
|
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + PointVisualisation.MAX_WIDTH * 2) / DrawWidth * Content.RelativeChildSize.X);
|
|
|
|
|
|
|
|
|
|
if (visibleRange != newRange)
|
2020-10-09 14:38:58 +08:00
|
|
|
|
{
|
2022-09-27 16:18:45 +08:00
|
|
|
|
visibleRange = newRange;
|
|
|
|
|
|
|
|
|
|
// actual regeneration only needs to occur if we've passed one of the known next min/max tick boundaries.
|
|
|
|
|
if (nextMinTick == null || nextMaxTick == null || (visibleRange.min < nextMinTick || visibleRange.max > nextMaxTick))
|
|
|
|
|
tickCache.Invalidate();
|
2020-10-09 14:38:58 +08:00
|
|
|
|
}
|
2020-01-27 16:34:25 +08:00
|
|
|
|
|
2020-10-09 14:57:31 +08:00
|
|
|
|
if (!tickCache.IsValid)
|
|
|
|
|
createTicks();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void createTicks()
|
|
|
|
|
{
|
|
|
|
|
int drawableIndex = 0;
|
2020-10-09 15:46:54 +08:00
|
|
|
|
|
|
|
|
|
nextMinTick = null;
|
|
|
|
|
nextMaxTick = null;
|
2020-10-09 14:57:31 +08:00
|
|
|
|
|
2021-10-27 12:04:41 +08:00
|
|
|
|
for (int i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++)
|
2020-01-27 16:34:25 +08:00
|
|
|
|
{
|
|
|
|
|
var point = beatmap.ControlPointInfo.TimingPoints[i];
|
2021-10-27 12:04:41 +08:00
|
|
|
|
double until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
|
|
|
|
|
int beat = 0;
|
|
|
|
|
|
|
|
|
|
for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value)
|
|
|
|
|
{
|
2020-10-09 15:46:54 +08:00
|
|
|
|
float xPos = (float)t;
|
|
|
|
|
|
|
|
|
|
if (t < visibleRange.min)
|
|
|
|
|
nextMinTick = xPos;
|
|
|
|
|
else if (t > visibleRange.max)
|
|
|
|
|
nextMaxTick ??= xPos;
|
|
|
|
|
else
|
2020-01-27 16:34:25 +08:00
|
|
|
|
{
|
2020-10-09 15:46:54 +08:00
|
|
|
|
// if this is the first beat in the beatmap, there is no next min tick
|
|
|
|
|
if (beat == 0 && i == 0)
|
|
|
|
|
nextMinTick = float.MinValue;
|
|
|
|
|
|
2022-01-23 00:27:27 +08:00
|
|
|
|
int indexInBar = beat % (point.TimeSignature.Numerator * beatDivisor.Value);
|
2020-01-27 16:34:25 +08:00
|
|
|
|
|
2021-10-27 12:04:41 +08:00
|
|
|
|
int divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value);
|
2020-10-09 15:46:54 +08:00
|
|
|
|
var colour = BindableBeatDivisor.GetColourFor(divisor, colours);
|
|
|
|
|
|
|
|
|
|
// even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn.
|
2021-04-13 15:25:43 +08:00
|
|
|
|
|
2022-05-24 16:02:19 +08:00
|
|
|
|
Vector2 size = Vector2.One;
|
|
|
|
|
|
2022-05-30 02:15:17 +08:00
|
|
|
|
if (indexInBar != 0)
|
2022-05-24 16:02:19 +08:00
|
|
|
|
size = BindableBeatDivisor.GetSize(divisor);
|
|
|
|
|
|
2021-04-13 15:25:43 +08:00
|
|
|
|
var line = getNextUsableLine();
|
|
|
|
|
line.X = xPos;
|
2022-05-24 16:02:19 +08:00
|
|
|
|
line.Width = PointVisualisation.MAX_WIDTH * size.X;
|
|
|
|
|
line.Height = 0.9f * size.Y;
|
2021-04-13 15:25:43 +08:00
|
|
|
|
line.Colour = colour;
|
2020-01-27 16:34:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
beat++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-09 14:38:58 +08:00
|
|
|
|
|
2022-09-27 16:11:36 +08:00
|
|
|
|
if (Children.Count > 512)
|
|
|
|
|
{
|
|
|
|
|
// There should always be a sanely small number of ticks rendered.
|
|
|
|
|
// If this assertion triggers, either the zoom logic is broken or a beatmap is
|
|
|
|
|
// probably doing weird things...
|
|
|
|
|
//
|
|
|
|
|
// Let's hope the latter never happens.
|
|
|
|
|
// If it does, we can choose to either fix it or ignore it as an outlier.
|
|
|
|
|
string message = $"Timeline is rendering many ticks ({Children.Count})";
|
|
|
|
|
|
|
|
|
|
Logger.Log(message);
|
|
|
|
|
Debug.Fail(message);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-09 14:38:58 +08:00
|
|
|
|
int usedDrawables = drawableIndex;
|
|
|
|
|
|
|
|
|
|
// save a few drawables beyond the currently used for edge cases.
|
|
|
|
|
while (drawableIndex < Math.Min(usedDrawables + 16, Count))
|
2024-03-16 20:05:52 +08:00
|
|
|
|
Children[drawableIndex++].Alpha = 0;
|
2020-10-09 14:38:58 +08:00
|
|
|
|
|
|
|
|
|
// expire any excess
|
|
|
|
|
while (drawableIndex < Count)
|
|
|
|
|
Children[drawableIndex++].Expire();
|
|
|
|
|
|
2020-10-09 14:57:31 +08:00
|
|
|
|
tickCache.Validate();
|
|
|
|
|
|
2021-04-13 15:25:43 +08:00
|
|
|
|
Drawable getNextUsableLine()
|
2020-10-09 14:38:58 +08:00
|
|
|
|
{
|
|
|
|
|
PointVisualisation point;
|
|
|
|
|
if (drawableIndex >= Count)
|
|
|
|
|
Add(point = new PointVisualisation());
|
|
|
|
|
else
|
2020-10-09 15:46:54 +08:00
|
|
|
|
point = Children[drawableIndex];
|
2020-10-09 14:38:58 +08:00
|
|
|
|
|
2020-10-09 15:46:54 +08:00
|
|
|
|
drawableIndex++;
|
2024-03-16 20:05:52 +08:00
|
|
|
|
point.Alpha = 1;
|
2020-10-09 14:38:58 +08:00
|
|
|
|
|
|
|
|
|
return point;
|
|
|
|
|
}
|
2020-01-27 16:34:25 +08:00
|
|
|
|
}
|
2020-12-01 15:44:08 +08:00
|
|
|
|
|
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
|
|
|
{
|
|
|
|
|
base.Dispose(isDisposing);
|
|
|
|
|
|
|
|
|
|
if (changeHandler != null)
|
|
|
|
|
changeHandler.OnStateChange -= invalidateTicks;
|
|
|
|
|
}
|
2020-01-27 16:34:25 +08:00
|
|
|
|
}
|
|
|
|
|
}
|