diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs
new file mode 100644
index 0000000000..5680b85c08
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs
@@ -0,0 +1,30 @@
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
+{
+ ///
+ /// The part of the timeline that displays bookmarks.
+ ///
+ internal class BookmarkPart : TimelinePart
+ {
+ protected override void LoadBeatmap(WorkingBeatmap beatmap)
+ {
+ foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks)
+ Add(new BookmarkVisualisation(bookmark));
+ }
+
+ private class BookmarkVisualisation : PointVisualisation
+ {
+ public BookmarkVisualisation(double startTime)
+ : base(startTime)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours) => Colour = colours.Blue;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
new file mode 100644
index 0000000000..fefb3d5f10
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
@@ -0,0 +1,31 @@
+using osu.Framework.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Graphics;
+using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
+{
+ ///
+ /// The part of the timeline that displays breaks in the song.
+ ///
+ internal class BreakPart : TimelinePart
+ {
+ protected override void LoadBeatmap(WorkingBeatmap beatmap)
+ {
+ foreach (var breakPeriod in beatmap.Beatmap.Breaks)
+ Add(new BreakVisualisation(breakPeriod));
+ }
+
+ private class BreakVisualisation : DurationVisualisation
+ {
+ public BreakVisualisation(BreakPeriod breakPeriod)
+ : base(breakPeriod.StartTime, breakPeriod.EndTime)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours) => Colour = colours.Yellow;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
new file mode 100644
index 0000000000..12b3624123
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
@@ -0,0 +1,65 @@
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Beatmaps;
+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
+{
+ ///
+ /// The part of the timeline that displays the control points.
+ ///
+ internal class ControlPointPart : TimelinePart
+ {
+ protected override void LoadBeatmap(WorkingBeatmap beatmap)
+ {
+ ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo;
+
+ cpi.TimingPoints.ForEach(addTimingPoint);
+
+ // Consider all non-timing points as the same type
+ cpi.SoundPoints.Select(c => (ControlPoint)c)
+ .Concat(cpi.EffectPoints)
+ .Concat(cpi.DifficultyPoints)
+ .Distinct()
+ // Non-timing points should not be added where there are timing points
+ .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time)
+ .ForEach(addNonTimingPoint);
+ }
+
+ private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint));
+ private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint));
+
+ private class TimingPointVisualisation : ControlPointVisualisation
+ {
+ public TimingPointVisualisation(ControlPoint controlPoint)
+ : base(controlPoint)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours) => Colour = colours.YellowDark;
+ }
+
+ private class NonTimingPointVisualisation : ControlPointVisualisation
+ {
+ public NonTimingPointVisualisation(ControlPoint controlPoint)
+ : base(controlPoint)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours) => Colour = colours.Green;
+ }
+
+ private abstract class ControlPointVisualisation : PointVisualisation
+ {
+ protected ControlPointVisualisation(ControlPoint controlPoint)
+ : base(controlPoint.Time)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
new file mode 100644
index 0000000000..30786a80ff
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs
@@ -0,0 +1,40 @@
+using System;
+using OpenTK;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
+{
+ ///
+ /// Represents a part of the summary timeline..
+ ///
+ internal abstract class TimelinePart : CompositeDrawable
+ {
+ private readonly Container timeline;
+
+ protected TimelinePart()
+ {
+ AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame)
+ {
+ osuGame.Beatmap.ValueChanged += b =>
+ {
+ timeline.Clear();
+ timeline.RelativeChildSize = new Vector2((float)Math.Max(1, b.Track.Length), 1);
+ LoadBeatmap(b);
+ };
+
+ timeline.RelativeChildSize = new Vector2((float)Math.Max(1, osuGame.Beatmap.Value.Track.Length), 1);
+ LoadBeatmap(osuGame.Beatmap);
+ }
+
+ protected void Add(Drawable visualisation) => timeline.Add(visualisation);
+
+ protected abstract void LoadBeatmap(WorkingBeatmap beatmap);
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs
new file mode 100644
index 0000000000..9718e9df5f
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs
@@ -0,0 +1,25 @@
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
+{
+ ///
+ /// Represents a spanning point on a .
+ ///
+ internal class DurationVisualisation : Container
+ {
+ protected DurationVisualisation(double startTime, double endTime)
+ {
+ Masking = true;
+ CornerRadius = 5;
+
+ RelativePositionAxes = Axes.X;
+ RelativeSizeAxes = Axes.Both;
+ X = (float)startTime;
+ Width = (float)(endTime - startTime);
+
+ AddInternal(new Box { RelativeSizeAxes = Axes.Both });
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs
new file mode 100644
index 0000000000..f0eb2ed499
--- /dev/null
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs
@@ -0,0 +1,24 @@
+using OpenTK;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+
+namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations
+{
+ ///
+ /// Represents a singular point on a .
+ ///
+ internal class PointVisualisation : Box
+ {
+ protected PointVisualisation(double startTime)
+ {
+ Origin = Anchor.TopCentre;
+
+ RelativeSizeAxes = Axes.Y;
+ Width = 1;
+ EdgeSmoothness = new Vector2(1, 0);
+
+ RelativePositionAxes = Axes.X;
+ X = (float)startTime;
+ }
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e43f6cfd8c..33eaff8ff5 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -606,6 +606,12 @@
+
+
+
+
+
+