diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index e7b2508ac7..3c75fd5310 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs @@ -1,146 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneTimelineBlueprintContainer : EditorClockTestScene + public class TestSceneTimelineBlueprintContainer : TimelineTestScene { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(TimelineArea), - typeof(Timeline), - typeof(TimelineButton), - typeof(CentreMarker) - }; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - Beatmap.Value = new WaveformTestBeatmap(audio); - - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); - - Dependencies.Cache(editorBeatmap); - Dependencies.CacheAs(editorBeatmap); - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] - { - new StartStopButton(), - new AudioVisualiser(), - } - }, - new TimelineArea - { - Child = new TimelineBlueprintContainer(), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Size = new Vector2(0.8f, 100) - } - }; - } - - private class AudioVisualiser : CompositeDrawable - { - private readonly Drawable marker; - - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved] - private IAdjustableClock adjustableClock { get; set; } - - public AudioVisualiser() - { - Size = new Vector2(250, 25); - - InternalChildren = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.25f, - }, - marker = new Box - { - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Width = 2, - } - }; - } - - protected override void Update() - { - base.Update(); - - if (beatmap.Value.Track.IsLoaded) - marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length); - } - } - - private class StartStopButton : OsuButton - { - private IAdjustableClock adjustableClock; - private bool started; - - public StartStopButton() - { - BackgroundColour = Color4.SlateGray; - Size = new Vector2(100, 50); - Text = "Start"; - - Action = onClick; - } - - [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock) - { - this.adjustableClock = adjustableClock; - } - - private void onClick() - { - if (started) - { - adjustableClock.Stop(); - Text = "Start"; - } - else - { - adjustableClock.Start(); - Text = "Stop"; - } - - started = !started; - } - } + public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(); } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs new file mode 100644 index 0000000000..43a3cd6122 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneTimelineTickDisplay : TimelineTestScene + { + public override Drawable CreateTestComponent() => new TimelineTickDisplay(); + + [BackgroundDependencyLoader] + private void load() + { + BeatDivisor.Value = 4; + + Add(new BeatDivisorControl(BeatDivisor) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(30), + Size = new Vector2(90) + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs new file mode 100644 index 0000000000..b5e526d3c2 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editor +{ + public abstract class TimelineTestScene : EditorClockTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TimelineArea), + typeof(Timeline), + typeof(TimelineButton), + typeof(CentreMarker) + }; + + protected TimelineArea TimelineArea { get; private set; } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + Beatmap.Value = new WaveformTestBeatmap(audio); + + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); + + Dependencies.Cache(editorBeatmap); + Dependencies.CacheAs(editorBeatmap); + + AddRange(new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new StartStopButton(), + new AudioVisualiser(), + } + }, + TimelineArea = new TimelineArea + { + Child = CreateTestComponent(), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Size = new Vector2(0.8f, 100), + } + }); + } + + public abstract Drawable CreateTestComponent(); + + private class AudioVisualiser : CompositeDrawable + { + private readonly Drawable marker; + + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IAdjustableClock adjustableClock { get; set; } + + public AudioVisualiser() + { + Size = new Vector2(250, 25); + + InternalChildren = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.25f, + }, + marker = new Box + { + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Width = 2, + } + }; + } + + protected override void Update() + { + base.Update(); + + if (beatmap.Value.Track.IsLoaded) + marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length); + } + } + + private class StartStopButton : OsuButton + { + private IAdjustableClock adjustableClock; + private bool started; + + public StartStopButton() + { + BackgroundColour = Color4.SlateGray; + Size = new Vector2(100, 50); + Text = "Start"; + + Action = onClick; + } + + [BackgroundDependencyLoader] + private void load(IAdjustableClock adjustableClock) + { + this.adjustableClock = adjustableClock; + } + + private void onClick() + { + if (started) + { + adjustableClock.Stop(); + Text = "Start"; + } + else + { + adjustableClock.Start(); + Text = "Stop"; + } + + started = !started; + } + } + } +} diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index ce95d81f54..d9477dd4bc 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -52,30 +52,52 @@ namespace osu.Game.Screens.Edit { switch (beatDivisor) { + case 1: + return Color4.White; + case 2: - return colours.BlueLight; + return colours.Red; case 4: return colours.Blue; case 8: - return colours.BlueDarker; + return colours.Yellow; case 16: return colours.PurpleDark; case 3: - return colours.YellowLight; + return colours.Purple; case 6: - return colours.Yellow; + return colours.YellowDark; case 12: return colours.YellowDarker; default: - return Color4.White; + return Color4.Red; } } + + /// + /// Retrieves the applicable divisor for a specific beat index. + /// + /// The 0-based beat index. + /// The beat divisor. + /// The applicable divisor. + public static int GetDivisorForBeatIndex(int index, int beatDivisor) + { + int beat = index % beatDivisor; + + foreach (var divisor in BindableBeatDivisor.VALID_DIVISORS) + { + if ((beat * divisor) % beatDivisor == 0) + return divisor; + } + + return 0; + } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 9c00cce57a..1ac960039e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public class PointVisualisation : Box { - protected PointVisualisation(double startTime) + public PointVisualisation(double startTime) { Origin = Anchor.TopCentre; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 53c5cf97fa..3bbccd612b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -130,19 +130,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The applicable colour. protected ColourInfo GetColourForBeatIndex(int index) { - int beat = (index + 1) % beatDivisor.Value; - ColourInfo colour = Colours.Gray5; - - for (int i = 0; i < BindableBeatDivisor.VALID_DIVISORS.Length; i++) - { - int divisor = BindableBeatDivisor.VALID_DIVISORS[i]; - - if ((beat * divisor) % beatDivisor.Value == 0) - { - colour = BindableBeatDivisor.GetColourFor(divisor, Colours); - break; - } - } + var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(index + 1, beatDivisor.Value), Colours); int repeatIndex = index / beatDivisor.Value; return colour.MultiplyAlpha(0.5f / (repeatIndex + 1)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 96395696c3..5b2dd343e6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -30,7 +30,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { ZoomDuration = 200; ZoomEasing = Easing.OutQuint; - Zoom = 10; + + Zoom = 60; + MaxZoom = 240; + ScrollbarVisible = false; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs new file mode 100644 index 0000000000..f9b92c0504 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -0,0 +1,90 @@ +// 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.Graphics; +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; + +namespace osu.Game.Screens.Edit.Compose.Components.Timeline +{ + public class TimelineTickDisplay : TimelinePart + { + [Resolved] + private EditorBeatmap beatmap { get; set; } + + [Resolved] + private Bindable working { get; set; } + + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + public TimelineTickDisplay() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + beatDivisor.BindValueChanged(_ => createLines(), true); + } + + private void createLines() + { + Clear(); + + for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) + { + var point = beatmap.ControlPointInfo.TimingPoints[i]; + var until = beatmap.ControlPointInfo.TimingPoints.Count < i + 1 ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + + int beat = 0; + + for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) + { + var indexInBeat = beat % beatDivisor.Value; + + if (indexInBeat == 0) + { + Add(new PointVisualisation(t) + { + Colour = BindableBeatDivisor.GetColourFor(1, colours), + Origin = Anchor.TopCentre, + }); + } + else + { + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + var colour = BindableBeatDivisor.GetColourFor(divisor, colours); + var height = 0.1f - (float)divisor / BindableBeatDivisor.VALID_DIVISORS.Last() * 0.08f; + + Add(new PointVisualisation(t) + { + Colour = colour, + Height = height, + Origin = Anchor.TopCentre, + }); + + Add(new PointVisualisation(t) + { + Colour = colour, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomCentre, + Height = height, + }); + } + + beat++; + } + } + } + } +} diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 8967f24185..7ee1005add 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -102,7 +102,11 @@ namespace osu.Game.Screens.Edit LoadComponentAsync(new TimelineArea { RelativeSizeAxes = Axes.Both, - Child = CreateTimelineContent() + Children = new[] + { + new TimelineTickDisplay(), + CreateTimelineContent(), + } }, timelineContainer.Add); }); }