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:
commit
07efd66287
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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());
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user