1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 12:57:36 +08:00

Simplify timing point display on timeline

This commit is contained in:
Dean Herbert 2024-08-21 00:50:41 +09:00
parent a0002943a1
commit 3065f808a7
No known key found for this signature in database
6 changed files with 168 additions and 273 deletions

View File

@ -78,7 +78,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private TimelineTickDisplay ticks = null!;
private TimelineControlPointDisplay controlPoints = null!;
private TimelineTimingChangeDisplay controlPoints = null!;
private Container mainContent = null!;
@ -117,10 +117,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
AddRange(new Drawable[]
{
controlPoints = new TimelineControlPointDisplay
ticks = new TimelineTickDisplay(),
controlPoints = new TimelineTimingChangeDisplay
{
RelativeSizeAxes = Axes.X,
Height = timeline_expanded_height,
Height = timeline_expanded_height - timeline_height,
},
ticks,
mainContent = new Container

View File

@ -1,98 +0,0 @@
// 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.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
/// <summary>
/// The part of the timeline that displays the control points.
/// </summary>
public partial class TimelineControlPointDisplay : TimelinePart<TimelineControlPointGroup>
{
[Resolved]
private Timeline timeline { get; set; } = null!;
/// <summary>
/// The visible time/position range of the timeline.
/// </summary>
private (float min, float max) visibleRange = (float.MinValue, float.MaxValue);
private readonly Cached groupCache = new Cached();
private readonly IBindableList<ControlPointGroup> controlPointGroups = new BindableList<ControlPointGroup>();
protected override void LoadBeatmap(EditorBeatmap beatmap)
{
base.LoadBeatmap(beatmap);
controlPointGroups.UnbindAll();
controlPointGroups.BindTo(beatmap.ControlPointInfo.Groups);
controlPointGroups.BindCollectionChanged((_, _) => groupCache.Invalidate(), true);
}
protected override void Update()
{
base.Update();
if (DrawWidth <= 0) return;
(float, float) newRange = (
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TopPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X,
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X) / DrawWidth * Content.RelativeChildSize.X);
if (visibleRange != newRange)
{
visibleRange = newRange;
groupCache.Invalidate();
}
if (!groupCache.IsValid)
{
recreateDrawableGroups();
groupCache.Validate();
}
}
private void recreateDrawableGroups()
{
// Remove groups outside the visible range
foreach (TimelineControlPointGroup drawableGroup in this)
{
if (!shouldBeVisible(drawableGroup.Group))
drawableGroup.Expire();
}
// Add remaining ones
for (int i = 0; i < controlPointGroups.Count; i++)
{
var group = controlPointGroups[i];
if (!shouldBeVisible(group))
continue;
bool alreadyVisible = false;
foreach (var g in this)
{
if (ReferenceEquals(g.Group, group))
{
alreadyVisible = true;
break;
}
}
if (alreadyVisible)
continue;
Add(new TimelineControlPointGroup(group));
}
}
private bool shouldBeVisible(ControlPointGroup group) => group.Time >= visibleRange.min && group.Time <= visibleRange.max;
}
}

View File

@ -1,52 +0,0 @@
// 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.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public partial class TimelineControlPointGroup : CompositeDrawable
{
public readonly ControlPointGroup Group;
private readonly IBindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>();
public TimelineControlPointGroup(ControlPointGroup group)
{
Group = group;
RelativePositionAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
Origin = Anchor.TopLeft;
// offset visually to avoid overlapping timeline tick display.
X = (float)group.Time + 6;
}
protected override void LoadComplete()
{
base.LoadComplete();
controlPoints.BindTo(Group.ControlPoints);
controlPoints.BindCollectionChanged((_, _) =>
{
ClearInternal();
foreach (var point in controlPoints)
{
switch (point)
{
case TimingControlPoint timingPoint:
AddInternal(new TimingPointPiece(timingPoint));
break;
}
}
}, true);
}
}
}

View File

@ -0,0 +1,164 @@
// 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.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
/// <summary>
/// The part of the timeline that displays the control points.
/// </summary>
public partial class TimelineTimingChangeDisplay : TimelinePart<TimelineTimingChangeDisplay.TimingPointPiece>
{
[Resolved]
private Timeline timeline { get; set; } = null!;
/// <summary>
/// The visible time/position range of the timeline.
/// </summary>
private (float min, float max) visibleRange = (float.MinValue, float.MaxValue);
private readonly Cached groupCache = new Cached();
private ControlPointInfo controlPointInfo = null!;
protected override void LoadBeatmap(EditorBeatmap beatmap)
{
base.LoadBeatmap(beatmap);
beatmap.ControlPointInfo.ControlPointsChanged += () => groupCache.Invalidate();
controlPointInfo = beatmap.ControlPointInfo;
}
protected override void Update()
{
base.Update();
if (DrawWidth <= 0) return;
(float, float) newRange = (
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopLeft).X - TimingPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X,
(ToLocalSpace(timeline.ScreenSpaceDrawQuad.TopRight).X + TimingPointPiece.WIDTH) / DrawWidth * Content.RelativeChildSize.X);
if (visibleRange != newRange)
{
visibleRange = newRange;
groupCache.Invalidate();
}
if (!groupCache.IsValid)
{
recreateDrawableGroups();
groupCache.Validate();
}
}
private void recreateDrawableGroups()
{
// Remove groups outside the visible range (or timing points which have since been removed from the beatmap).
foreach (TimingPointPiece drawableGroup in this)
{
if (!controlPointInfo.TimingPoints.Contains(drawableGroup.Point) || !shouldBeVisible(drawableGroup.Point))
drawableGroup.Expire();
}
// Add remaining / new ones.
foreach (TimingControlPoint t in controlPointInfo.TimingPoints)
attemptAddTimingPoint(t);
}
private void attemptAddTimingPoint(TimingControlPoint point)
{
if (!shouldBeVisible(point))
return;
foreach (var child in this)
{
if (ReferenceEquals(child.Point, point))
return;
}
Add(new TimingPointPiece(point));
}
private bool shouldBeVisible(TimingControlPoint point) => point.Time >= visibleRange.min && point.Time <= visibleRange.max;
public partial class TimingPointPiece : CompositeDrawable
{
public const float WIDTH = 16;
public readonly TimingControlPoint Point;
private readonly BindableNumber<double> beatLength;
protected OsuSpriteText Label { get; private set; } = null!;
public TimingPointPiece(TimingControlPoint timingPoint)
{
RelativePositionAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
Width = WIDTH;
Origin = Anchor.TopRight;
Point = timingPoint;
beatLength = timingPoint.BeatLengthBindable.GetBoundCopy();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
const float corner_radius = PointVisualisation.MAX_WIDTH / 2;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Colour = Point.GetRepresentingColour(colours),
Masking = true,
CornerRadius = corner_radius,
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
},
Label = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Rotation = 90,
Padding = new MarginPadding { Horizontal = 2 },
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
}
};
beatLength.BindValueChanged(beatLength =>
{
Label.Text = $"{60000 / beatLength.NewValue:n1} BPM";
}, true);
}
protected override void Update()
{
base.Update();
X = (float)Point.Time;
}
}
}
}

View File

@ -1,29 +0,0 @@
// 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.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public partial class TimingPointPiece : TopPointPiece
{
private readonly BindableNumber<double> beatLength;
public TimingPointPiece(TimingControlPoint point)
: base(point)
{
beatLength = point.BeatLengthBindable.GetBoundCopy();
}
[BackgroundDependencyLoader]
private void load()
{
beatLength.BindValueChanged(beatLength =>
{
Label.Text = $"{60000 / beatLength.NewValue:n1} BPM";
}, true);
}
}
}

View File

@ -1,91 +0,0 @@
// 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.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
public partial class TopPointPiece : CompositeDrawable
{
protected readonly ControlPoint Point;
protected OsuSpriteText Label { get; private set; } = null!;
public const float WIDTH = 80;
public TopPointPiece(ControlPoint point)
{
Point = point;
Width = WIDTH;
Height = 16;
Margin = new MarginPadding { Vertical = 4 };
Origin = Anchor.TopCentre;
Anchor = Anchor.TopCentre;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
const float corner_radius = 4;
const float arrow_extension = 3;
const float triangle_portion = 15;
InternalChildren = new Drawable[]
{
// This is a triangle, trust me.
// Doing it this way looks okay. Doing it using Triangle primitive is basically impossible.
new Container
{
Colour = Point.GetRepresentingColour(colours),
X = -corner_radius,
Size = new Vector2(triangle_portion * arrow_extension, Height),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Masking = true,
CornerRadius = Height,
CornerExponent = 1.4f,
Children = new Drawable[]
{
new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
}
},
new Container
{
RelativeSizeAxes = Axes.Y,
Width = WIDTH - triangle_portion,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Colour = Point.GetRepresentingColour(colours),
Masking = true,
CornerRadius = corner_radius,
Child = new Box
{
Colour = Color4.White,
RelativeSizeAxes = Axes.Both,
},
},
Label = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Padding = new MarginPadding(3),
Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold),
Colour = colours.B5,
}
};
}
}
}