mirror of
https://github.com/ppy/osu.git
synced 2024-12-16 17:02:55 +08:00
Merge pull request #7734 from peppy/editor-slider-repeat
Add the ability to extend hold notes (spinners / sliders etc.) via timeline
This commit is contained in:
commit
8d12d820f1
@ -36,7 +36,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double EndTime => StartTime + Duration;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + Duration;
|
||||||
|
set => Duration = value - StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
public double Duration { get; set; }
|
public double Duration { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,11 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||||
|
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||||
|
}
|
||||||
|
|
||||||
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
|
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
|
||||||
|
|
||||||
|
@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class HoldNote : ManiaHitObject, IHasEndTime
|
public class HoldNote : ManiaHitObject, IHasEndTime
|
||||||
{
|
{
|
||||||
public double EndTime => StartTime + Duration;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + Duration;
|
||||||
|
set => Duration = value - StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
private double duration;
|
private double duration;
|
||||||
|
|
||||||
|
@ -18,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
public class Slider : OsuHitObject, IHasCurve
|
public class Slider : OsuHitObject, IHasCurve
|
||||||
{
|
{
|
||||||
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
||||||
|
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||||
|
}
|
||||||
|
|
||||||
public double Duration => EndTime - StartTime;
|
public double Duration => EndTime - StartTime;
|
||||||
|
|
||||||
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
||||||
@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
repeatCount = value;
|
repeatCount = value;
|
||||||
endPositionCache.Invalidate();
|
updateNestedPositions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const float base_distance = 100;
|
private const float base_distance = 100;
|
||||||
|
|
||||||
public double EndTime => StartTime + Duration;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + Duration;
|
||||||
|
set => Duration = value - StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
public double Duration { get; set; }
|
public double Duration { get; set; }
|
||||||
|
|
||||||
|
@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
{
|
{
|
||||||
public class Swell : TaikoHitObject, IHasEndTime
|
public class Swell : TaikoHitObject, IHasEndTime
|
||||||
{
|
{
|
||||||
public double EndTime => StartTime + Duration;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + Duration;
|
||||||
|
set => Duration = value - StartTime;
|
||||||
|
}
|
||||||
|
|
||||||
public double Duration { get; set; }
|
public double Duration { get; set; }
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
@ -10,6 +12,17 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
|
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
|
||||||
{
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(TimelineHitObjectBlueprint),
|
||||||
|
};
|
||||||
|
|
||||||
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
|
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Clock.Seek(10000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,12 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
};
|
};
|
||||||
|
|
||||||
[Cached(typeof(EditorBeatmap))]
|
[Cached(typeof(EditorBeatmap))]
|
||||||
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
|
public TestSceneTimingScreen()
|
||||||
|
{
|
||||||
|
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -13,7 +13,6 @@ 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.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Screens.Edit;
|
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;
|
||||||
@ -38,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
{
|
{
|
||||||
Beatmap.Value = new WaveformTestBeatmap(audio);
|
Beatmap.Value = new WaveformTestBeatmap(audio);
|
||||||
|
|
||||||
var editorBeatmap = new EditorBeatmap((Beatmap<HitObject>)Beatmap.Value.Beatmap);
|
var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
|
||||||
|
|
||||||
|
var editorBeatmap = new EditorBeatmap(playable);
|
||||||
|
|
||||||
Dependencies.Cache(editorBeatmap);
|
Dependencies.Cache(editorBeatmap);
|
||||||
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
|
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
|
||||||
|
@ -11,6 +11,8 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Rulesets.Catch;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests
|
namespace osu.Game.Tests
|
||||||
@ -20,11 +22,18 @@ namespace osu.Game.Tests
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class WaveformTestBeatmap : WorkingBeatmap
|
public class WaveformTestBeatmap : WorkingBeatmap
|
||||||
{
|
{
|
||||||
|
private readonly Beatmap beatmap;
|
||||||
private readonly ITrackStore trackStore;
|
private readonly ITrackStore trackStore;
|
||||||
|
|
||||||
public WaveformTestBeatmap(AudioManager audioManager)
|
public WaveformTestBeatmap(AudioManager audioManager)
|
||||||
: base(new BeatmapInfo(), audioManager)
|
: this(audioManager, new WaveformBeatmap())
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaveformTestBeatmap(AudioManager audioManager, Beatmap beatmap)
|
||||||
|
: base(beatmap.BeatmapInfo, audioManager)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
trackStore = audioManager.GetTrackStore(getZipReader());
|
trackStore = audioManager.GetTrackStore(getZipReader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +43,11 @@ namespace osu.Game.Tests
|
|||||||
trackStore?.Dispose();
|
trackStore?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream getStream() => TestResources.GetTestBeatmapStream();
|
private static Stream getStream() => TestResources.GetTestBeatmapStream();
|
||||||
|
|
||||||
private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream());
|
private static ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream());
|
||||||
|
|
||||||
protected override IBeatmap GetBeatmap() => createTestBeatmap();
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
|
|
||||||
protected override Texture GetBackground() => null;
|
protected override Texture GetBackground() => null;
|
||||||
|
|
||||||
@ -57,10 +66,16 @@ namespace osu.Game.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Beatmap createTestBeatmap()
|
private class WaveformBeatmap : TestBeatmap
|
||||||
|
{
|
||||||
|
public WaveformBeatmap()
|
||||||
|
: base(new CatchRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Beatmap CreateBeatmap()
|
||||||
{
|
{
|
||||||
using (var reader = getZipReader())
|
using (var reader = getZipReader())
|
||||||
{
|
|
||||||
using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))
|
using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))
|
||||||
using (var beatmapReader = new LineBufferedReader(beatmapStream))
|
using (var beatmapReader = new LineBufferedReader(beatmapStream))
|
||||||
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
|
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
|
||||||
|
@ -26,7 +26,12 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
|
||||||
public int RepeatCount { get; set; }
|
public int RepeatCount { get; set; }
|
||||||
|
|
||||||
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
|
public double EndTime
|
||||||
|
{
|
||||||
|
get => StartTime + this.SpanCount() * Distance / Velocity;
|
||||||
|
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||||
|
}
|
||||||
|
|
||||||
public double Duration => EndTime - StartTime;
|
public double Duration => EndTime - StartTime;
|
||||||
|
|
||||||
public double Velocity = 1;
|
public double Velocity = 1;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// 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 Newtonsoft.Json;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -11,7 +13,8 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the HitObject ends.
|
/// The time at which the HitObject ends.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double EndTime { get; }
|
[JsonIgnore]
|
||||||
|
double EndTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The duration of the HitObject.
|
/// The duration of the HitObject.
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of times the HitObject repeats.
|
/// The amount of times the HitObject repeats.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int RepeatCount { get; }
|
int RepeatCount { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The samples to be played when each node of the <see cref="IHasRepeats"/> is hit.<br />
|
/// The samples to be played when each node of the <see cref="IHasRepeats"/> is hit.<br />
|
||||||
|
@ -179,11 +179,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||||
|
|
||||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
|
public double GetTimeFromScreenSpacePosition(Vector2 position)
|
||||||
{
|
=> getTimeFromPosition(Content.ToLocalSpace(position));
|
||||||
var targetTime = (position.X / Content.DrawWidth) * track.Length;
|
|
||||||
return (position, beatSnapProvider.SnapTime(targetTime));
|
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) =>
|
||||||
}
|
(position, beatSnapProvider.SnapTime(getTimeFromPosition(position)));
|
||||||
|
|
||||||
|
private double getTimeFromPosition(Vector2 localPosition) =>
|
||||||
|
(localPosition.X / Content.DrawWidth) * track.Length;
|
||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException();
|
public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -49,20 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
if (timeline != null)
|
handleScrollViaDrag(e);
|
||||||
{
|
|
||||||
var timelineQuad = timeline.ScreenSpaceDrawQuad;
|
|
||||||
var mouseX = e.ScreenSpaceMousePosition.X;
|
|
||||||
|
|
||||||
// scroll if in a drag and dragging outside visible extents
|
|
||||||
if (mouseX > timelineQuad.TopRight.X)
|
|
||||||
timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
|
|
||||||
else if (mouseX < timelineQuad.TopLeft.X)
|
|
||||||
timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnDrag(e);
|
base.OnDrag(e);
|
||||||
lastDragEvent = e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
@ -74,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
// trigger every frame so drags continue to update selection while playback is scrolling the timeline.
|
// trigger every frame so drags continue to update selection while playback is scrolling the timeline.
|
||||||
if (IsDragged)
|
if (lastDragEvent != null)
|
||||||
OnDrag(lastDragEvent);
|
OnDrag(lastDragEvent);
|
||||||
|
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -82,10 +71,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler();
|
protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler();
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject);
|
protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject)
|
||||||
|
{
|
||||||
|
OnDragHandled = handleScrollViaDrag
|
||||||
|
};
|
||||||
|
|
||||||
protected override DragBox CreateDragBox(Action<RectangleF> performSelect) => new TimelineDragBox(performSelect);
|
protected override DragBox CreateDragBox(Action<RectangleF> performSelect) => new TimelineDragBox(performSelect);
|
||||||
|
|
||||||
|
private void handleScrollViaDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
lastDragEvent = e;
|
||||||
|
|
||||||
|
if (lastDragEvent == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (timeline != null)
|
||||||
|
{
|
||||||
|
var timelineQuad = timeline.ScreenSpaceDrawQuad;
|
||||||
|
var mouseX = e.ScreenSpaceMousePosition.X;
|
||||||
|
|
||||||
|
// scroll if in a drag and dragging outside visible extents
|
||||||
|
if (mouseX > timelineQuad.TopRight.X)
|
||||||
|
timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime));
|
||||||
|
else if (mouseX < timelineQuad.TopLeft.X)
|
||||||
|
timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal class TimelineSelectionHandler : SelectionHandler
|
internal class TimelineSelectionHandler : SelectionHandler
|
||||||
{
|
{
|
||||||
// for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation
|
// for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
// 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 System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -19,17 +25,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
private readonly Circle circle;
|
private readonly Circle circle;
|
||||||
|
|
||||||
private readonly Container extensionBar;
|
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
private readonly Bindable<double> startTime;
|
private readonly Bindable<double> startTime;
|
||||||
|
|
||||||
public const float THICKNESS = 3;
|
public Action<DragEvent> OnDragHandled;
|
||||||
|
|
||||||
|
private readonly DragBar dragBar;
|
||||||
|
|
||||||
|
private readonly List<Container> shadowComponents = new List<Container>();
|
||||||
|
|
||||||
|
private const float thickness = 5;
|
||||||
|
|
||||||
|
private const float shadow_radius = 5;
|
||||||
|
|
||||||
private const float circle_size = 16;
|
private const float circle_size = 16;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos);
|
|
||||||
|
|
||||||
public TimelineHitObjectBlueprint(HitObject hitObject)
|
public TimelineHitObjectBlueprint(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
@ -44,26 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
if (hitObject is IHasEndTime)
|
circle = new Circle
|
||||||
{
|
|
||||||
AddInternal(extensionBar = 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(circle = new Circle
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(circle_size),
|
Size = new Vector2(circle_size),
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -71,9 +62,65 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativePositionAxes = Axes.X,
|
RelativePositionAxes = Axes.X,
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Colour = Color4.White,
|
Colour = Color4.White,
|
||||||
BorderColour = Color4.Black,
|
EdgeEffect = new EdgeEffectParameters
|
||||||
BorderThickness = THICKNESS,
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = shadow_radius,
|
||||||
|
Colour = Color4.Black
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
shadowComponents.Add(circle);
|
||||||
|
|
||||||
|
if (hitObject is IHasEndTime)
|
||||||
|
{
|
||||||
|
DragBar dragBarUnderlay;
|
||||||
|
Container extensionBar;
|
||||||
|
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
extensionBar = new Container
|
||||||
|
{
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(1, thickness),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = shadow_radius,
|
||||||
|
Colour = Color4.Black
|
||||||
|
},
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
circle,
|
||||||
|
// only used for drawing the shadow
|
||||||
|
dragBarUnderlay = new DragBar(null),
|
||||||
|
// cover up the shadow on the join
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Height = thickness,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
},
|
||||||
|
dragBar = new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
shadowComponents.Add(dragBarUnderlay);
|
||||||
|
shadowComponents.Add(extensionBar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddInternal(circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateShadows();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -84,18 +131,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Width = (float)(HitObject.GetEndTime() - HitObject.StartTime);
|
Width = (float)(HitObject.GetEndTime() - HitObject.StartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldBeConsideredForInput(Drawable child) => true;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
|
base.ReceivePositionalInputAt(screenSpacePos) ||
|
||||||
|
circle.ReceivePositionalInputAt(screenSpacePos) ||
|
||||||
|
dragBar?.ReceivePositionalInputAt(screenSpacePos) == true;
|
||||||
|
|
||||||
protected override void OnSelected()
|
protected override void OnSelected()
|
||||||
{
|
{
|
||||||
circle.BorderColour = Color4.Orange;
|
updateShadows();
|
||||||
if (extensionBar != null)
|
}
|
||||||
extensionBar.Colour = Color4.Orange;
|
|
||||||
|
private void updateShadows()
|
||||||
|
{
|
||||||
|
foreach (var s in shadowComponents)
|
||||||
|
{
|
||||||
|
if (State == SelectionState.Selected)
|
||||||
|
{
|
||||||
|
s.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = shadow_radius / 2,
|
||||||
|
Colour = Color4.Orange,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = shadow_radius,
|
||||||
|
Colour = State == SelectionState.Selected ? Color4.Orange : Color4.Black
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDeselected()
|
protected override void OnDeselected()
|
||||||
{
|
{
|
||||||
circle.BorderColour = Color4.Black;
|
updateShadows();
|
||||||
if (extensionBar != null)
|
|
||||||
extensionBar.Colour = Color4.Black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Quad SelectionQuad
|
public override Quad SelectionQuad
|
||||||
@ -103,14 +178,130 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
// correctly include the circle in the selection quad region, as it is usually outside the blueprint itself.
|
// correctly include the circle in the selection quad region, as it is usually outside the blueprint itself.
|
||||||
var circleQuad = circle.ScreenSpaceDrawQuad;
|
var leftQuad = circle.ScreenSpaceDrawQuad;
|
||||||
var actualQuad = ScreenSpaceDrawQuad;
|
var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight),
|
return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight),
|
||||||
circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight));
|
leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
|
||||||
|
|
||||||
|
public class DragBar : Container
|
||||||
|
{
|
||||||
|
private readonly HitObject hitObject;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Timeline timeline { get; set; }
|
||||||
|
|
||||||
|
public Action<DragEvent> OnDragHandled;
|
||||||
|
|
||||||
|
public override bool HandlePositionalInput => hitObject != null;
|
||||||
|
|
||||||
|
public DragBar(HitObject hitObject)
|
||||||
|
{
|
||||||
|
this.hitObject = hitObject;
|
||||||
|
|
||||||
|
CornerRadius = 2;
|
||||||
|
Masking = true;
|
||||||
|
Size = new Vector2(5, 1);
|
||||||
|
Anchor = Anchor.CentreRight;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
RelativePositionAxes = Axes.X;
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool hasMouseDown;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
hasMouseDown = true;
|
||||||
|
updateState();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
hasMouseDown = false;
|
||||||
|
updateState();
|
||||||
|
base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
base.OnDrag(e);
|
||||||
|
|
||||||
|
OnDragHandled?.Invoke(e);
|
||||||
|
|
||||||
|
var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case IHasRepeats repeatHitObject:
|
||||||
|
// find the number of repeats which can fit in the requested time.
|
||||||
|
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
||||||
|
var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
||||||
|
|
||||||
|
if (proposedCount == repeatHitObject.RepeatCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IHasEndTime endTimeHitObject:
|
||||||
|
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
||||||
|
|
||||||
|
if (endTimeHitObject.EndTime == snappedTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
endTimeHitObject.EndTime = snappedTime;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmap.UpdateHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
|
||||||
|
OnDragHandled?.Invoke(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
{
|
{
|
||||||
public TestBeatmap(RulesetInfo ruleset)
|
public TestBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
var baseBeatmap = createTestBeatmap();
|
var baseBeatmap = CreateBeatmap();
|
||||||
|
|
||||||
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
||||||
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
||||||
@ -37,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Beatmap CreateBeatmap() => createTestBeatmap();
|
||||||
|
|
||||||
private static Beatmap createTestBeatmap()
|
private static Beatmap createTestBeatmap()
|
||||||
{
|
{
|
||||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
|
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
|
||||||
|
Loading…
Reference in New Issue
Block a user