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

Merge pull request #7075 from peppy/editor-timeline-hitobject-display

Editor timeline hitobject display
This commit is contained in:
Dan Balasescu 2019-12-09 14:53:25 +09:00 committed by GitHub
commit 07efd66287
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 165 additions and 32 deletions

View File

@ -13,6 +13,8 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -25,6 +27,7 @@ namespace osu.Game.Tests.Visual.Editor
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(TimelineArea), typeof(TimelineArea),
typeof(TimelineHitObjectDisplay),
typeof(Timeline), typeof(Timeline),
typeof(TimelineButton), typeof(TimelineButton),
typeof(CentreMarker) typeof(CentreMarker)
@ -35,6 +38,8 @@ namespace osu.Game.Tests.Visual.Editor
{ {
Beatmap.Value = new WaveformTestBeatmap(audio); Beatmap.Value = new WaveformTestBeatmap(audio);
var editorBeatmap = new EditorBeatmap<HitObject>((Beatmap<HitObject>)Beatmap.Value.Beatmap);
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
@ -50,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor
}, },
new TimelineArea new TimelineArea
{ {
Child = new TimelineHitObjectDisplay(editorBeatmap),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit
where TObject : HitObject where TObject : HitObject
{ {
protected IRulesetConfigManager Config { get; private set; } protected IRulesetConfigManager Config { get; private set; }
protected EditorBeatmap<TObject> EditorBeatmap { get; private set; }
protected new EditorBeatmap<TObject> EditorBeatmap { get; private set; }
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
[Resolved] [Resolved]
@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Edit
beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap); base.EditorBeatmap = EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectAdded += addHitObject;
EditorBeatmap.HitObjectRemoved += removeHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject;
EditorBeatmap.StartTimeChanged += UpdateHitObject; EditorBeatmap.StartTimeChanged += UpdateHitObject;
@ -333,6 +335,11 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public abstract IEnumerable<DrawableHitObject> HitObjects { get; } public abstract IEnumerable<DrawableHitObject> HitObjects { get; }
/// <summary>
/// An editor-specific beatmap, exposing mutation events.
/// </summary>
public IEditorBeatmap EditorBeatmap { get; protected set; }
/// <summary> /// <summary>
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement. /// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
/// </summary> /// </summary>

View File

@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
/// <summary> /// <summary>
/// Represents a part of the summary timeline.. /// Represents a part of the summary timeline..
/// </summary> /// </summary>
public abstract class TimelinePart : CompositeDrawable public abstract class TimelinePart : Container
{ {
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>(); protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private readonly Container timeline; private readonly Container timeline;
protected override Container<Drawable> Content => timeline;
protected TimelinePart() protected TimelinePart()
{ {
AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both });
@ -50,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
} }
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
protected virtual void LoadBeatmap(WorkingBeatmap beatmap) protected virtual void LoadBeatmap(WorkingBeatmap beatmap)
{ {
timeline.Clear(); timeline.Clear();

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
this.adjustableClock = adjustableClock; this.adjustableClock = adjustableClock;
Child = waveform = new WaveformGraph Add(waveform = new WaveformGraph
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.Blue.Opacity(0.2f), Colour = colours.Blue.Opacity(0.2f),
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
MidColour = colours.BlueDark, MidColour = colours.BlueDark,
HighColour = colours.BlueDarker, HighColour = colours.BlueDarker,
Depth = float.MaxValue Depth = float.MaxValue
}; });
// We don't want the centre marker to scroll // We don't want the centre marker to scroll
AddInternal(new CentreMarker()); AddInternal(new CentreMarker());

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -11,17 +12,18 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
public class TimelineArea : CompositeDrawable public class TimelineArea : Container
{ {
private readonly Timeline timeline; private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both };
public TimelineArea() protected override Container<Drawable> Content => timeline;
[BackgroundDependencyLoader]
private void load()
{ {
Masking = true; Masking = true;
CornerRadius = 5; CornerRadius = 5;
OsuCheckbox hitObjectsCheckbox;
OsuCheckbox hitSoundsCheckbox;
OsuCheckbox waveformCheckbox; OsuCheckbox waveformCheckbox;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -60,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
Spacing = new Vector2(0, 4), Spacing = new Vector2(0, 4),
Children = new[] Children = new[]
{ {
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" },
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" },
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
} }
} }
@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
} }
}, },
timeline = new Timeline { RelativeSizeAxes = Axes.Both } timeline
}, },
}, },
ColumnDimensions = new[] ColumnDimensions = new[]
@ -119,8 +119,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
} }
}; };
hitObjectsCheckbox.Current.Value = true;
hitSoundsCheckbox.Current.Value = true;
waveformCheckbox.Current.Value = true; waveformCheckbox.Current.Value = true;
timeline.WaveformVisible.BindTo(waveformCheckbox.Current); timeline.WaveformVisible.BindTo(waveformCheckbox.Current);

View File

@ -0,0 +1,108 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
internal class TimelineHitObjectDisplay : TimelinePart
{
private IEditorBeatmap beatmap { get; }
public TimelineHitObjectDisplay(IEditorBeatmap beatmap)
{
RelativeSizeAxes = Axes.Both;
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load()
{
foreach (var h in beatmap.HitObjects)
add(h);
beatmap.HitObjectAdded += add;
beatmap.HitObjectRemoved += remove;
beatmap.StartTimeChanged += h =>
{
remove(h);
add(h);
};
}
private void remove(HitObject h)
{
foreach (var d in Children.OfType<TimelineHitObjectRepresentation>().Where(c => c.HitObject == h))
d.Expire();
}
private void add(HitObject h)
{
var yOffset = Children.Count(d => d.X == h.StartTime);
Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS });
}
private class TimelineHitObjectRepresentation : CompositeDrawable
{
public const float THICKNESS = 3;
public readonly HitObject HitObject;
public TimelineHitObjectRepresentation(HitObject hitObject)
{
HitObject = hitObject;
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
Width = (float)(hitObject.GetEndTime() - hitObject.StartTime);
X = (float)hitObject.StartTime;
RelativePositionAxes = Axes.X;
RelativeSizeAxes = Axes.X;
if (hitObject is IHasEndTime)
{
AddInternal(new Container
{
CornerRadius = 2,
Masking = true,
Size = new Vector2(1, THICKNESS),
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativePositionAxes = Axes.X,
RelativeSizeAxes = Axes.X,
Colour = Color4.Black,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
}
});
}
AddInternal(new Circle
{
Size = new Vector2(16),
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
AlwaysPresent = true,
Colour = Color4.White,
BorderColour = Color4.Black,
BorderThickness = THICKNESS,
});
}
}
}
}

View File

@ -3,32 +3,35 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning; using osu.Game.Skinning;
namespace osu.Game.Screens.Edit.Compose namespace osu.Game.Screens.Edit.Compose
{ {
public class ComposeScreen : EditorScreenWithTimeline public class ComposeScreen : EditorScreenWithTimeline
{ {
private HitObjectComposer composer;
protected override Drawable CreateMainContent() protected override Drawable CreateMainContent()
{ {
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
composer = ruleset?.CreateHitObjectComposer();
var composer = ruleset?.CreateHitObjectComposer(); if (ruleset == null || composer == null)
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
if (composer != null) var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
{
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources. // full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
// load the skinning hierarchy first. // load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer())); return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
}
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
} }
protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap);
} }
} }

View File

@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private TimelineArea timelineArea;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor) private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{ {
@ -64,7 +66,7 @@ namespace osu.Game.Screens.Edit
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 }, Padding = new MarginPadding { Right = 5 },
Child = CreateTimeline() Child = timelineArea = CreateTimelineArea()
}, },
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
}, },
@ -97,11 +99,15 @@ namespace osu.Game.Screens.Edit
{ {
mainContent.Add(content); mainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint); content.FadeInFromZero(300, Easing.OutQuint);
LoadComponentAsync(CreateTimelineContent(), timelineArea.Add);
}); });
} }
protected abstract Drawable CreateMainContent(); protected abstract Drawable CreateMainContent();
protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both }; protected virtual Drawable CreateTimelineContent() => new Container();
protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both };
} }
} }

View File

@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit
/// Invoked when a <see cref="HitObject"/> is removed from this <see cref="IEditorBeatmap"/>. /// Invoked when a <see cref="HitObject"/> is removed from this <see cref="IEditorBeatmap"/>.
/// </summary> /// </summary>
event Action<HitObject> HitObjectRemoved; event Action<HitObject> HitObjectRemoved;
/// <summary>
/// Invoked when the start time of a <see cref="HitObject"/> in this <see cref="EditorBeatmap{T}"/> was changed.
/// </summary>
event Action<HitObject> StartTimeChanged;
} }
/// <summary> /// <summary>