mirror of
https://github.com/ppy/osu
synced 2024-12-17 12:25:19 +00: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; }
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// </summary>
|
||||
public class HoldNote : ManiaHitObject, IHasEndTime
|
||||
{
|
||||
public double EndTime => StartTime + Duration;
|
||||
public double EndTime
|
||||
{
|
||||
get => StartTime + Duration;
|
||||
set => Duration = value - StartTime;
|
||||
}
|
||||
|
||||
private double duration;
|
||||
|
||||
|
@ -18,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
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;
|
||||
|
||||
private readonly Cached<Vector2> endPositionCache = new Cached<Vector2>();
|
||||
@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
set
|
||||
{
|
||||
repeatCount = value;
|
||||
endPositionCache.Invalidate();
|
||||
updateNestedPositions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// </summary>
|
||||
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; }
|
||||
|
||||
|
@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
{
|
||||
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; }
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
@ -10,6 +12,17 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
[TestFixture]
|
||||
public class TestSceneTimelineBlueprintContainer : TimelineTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(TimelineHitObjectBlueprint),
|
||||
};
|
||||
|
||||
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))]
|
||||
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
public TestSceneTimingScreen()
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
|
@ -13,7 +13,6 @@ 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;
|
||||
@ -38,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
{
|
||||
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.CacheAs<IBeatSnapProvider>(editorBeatmap);
|
||||
|
@ -11,6 +11,8 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests
|
||||
@ -20,11 +22,18 @@ namespace osu.Game.Tests
|
||||
/// </summary>
|
||||
public class WaveformTestBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly Beatmap beatmap;
|
||||
private readonly ITrackStore trackStore;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@ -34,11 +43,11 @@ namespace osu.Game.Tests
|
||||
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;
|
||||
|
||||
@ -57,10 +66,16 @@ namespace osu.Game.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private Beatmap createTestBeatmap()
|
||||
private class WaveformBeatmap : TestBeatmap
|
||||
{
|
||||
using (var reader = getZipReader())
|
||||
public WaveformBeatmap()
|
||||
: base(new CatchRuleset().RulesetInfo)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Beatmap CreateBeatmap()
|
||||
{
|
||||
using (var reader = getZipReader())
|
||||
using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))))
|
||||
using (var beatmapReader = new LineBufferedReader(beatmapStream))
|
||||
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 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 Velocity = 1;
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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 Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Types
|
||||
{
|
||||
/// <summary>
|
||||
@ -11,7 +13,8 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// The time at which the HitObject ends.
|
||||
/// </summary>
|
||||
double EndTime { get; }
|
||||
[JsonIgnore]
|
||||
double EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the HitObject.
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types
|
||||
/// <summary>
|
||||
/// The amount of times the HitObject repeats.
|
||||
/// </summary>
|
||||
int RepeatCount { get; }
|
||||
int RepeatCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||
|
||||
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
|
||||
{
|
||||
var targetTime = (position.X / Content.DrawWidth) * track.Length;
|
||||
return (position, beatSnapProvider.SnapTime(targetTime));
|
||||
}
|
||||
public double GetTimeFromScreenSpacePosition(Vector2 position)
|
||||
=> getTimeFromPosition(Content.ToLocalSpace(position));
|
||||
|
||||
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();
|
||||
|
||||
|
@ -49,20 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
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));
|
||||
}
|
||||
handleScrollViaDrag(e);
|
||||
|
||||
base.OnDrag(e);
|
||||
lastDragEvent = e;
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
@ -74,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
protected override void Update()
|
||||
{
|
||||
// trigger every frame so drags continue to update selection while playback is scrolling the timeline.
|
||||
if (IsDragged)
|
||||
if (lastDragEvent != null)
|
||||
OnDrag(lastDragEvent);
|
||||
|
||||
base.Update();
|
||||
@ -82,10 +71,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
{
|
||||
// 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.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@ -19,17 +25,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
{
|
||||
private readonly Circle circle;
|
||||
|
||||
private readonly Container extensionBar;
|
||||
|
||||
[UsedImplicitly]
|
||||
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;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public TimelineHitObjectBlueprint(HitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
@ -44,26 +54,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
if (hitObject is IHasEndTime)
|
||||
{
|
||||
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
|
||||
circle = new Circle
|
||||
{
|
||||
Size = new Vector2(circle_size),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
@ -71,9 +62,65 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
RelativePositionAxes = Axes.X,
|
||||
AlwaysPresent = true,
|
||||
Colour = Color4.White,
|
||||
BorderColour = Color4.Black,
|
||||
BorderThickness = THICKNESS,
|
||||
});
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
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()
|
||||
@ -84,18 +131,46 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
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()
|
||||
{
|
||||
circle.BorderColour = Color4.Orange;
|
||||
if (extensionBar != null)
|
||||
extensionBar.Colour = Color4.Orange;
|
||||
updateShadows();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
circle.BorderColour = Color4.Black;
|
||||
if (extensionBar != null)
|
||||
extensionBar.Colour = Color4.Black;
|
||||
updateShadows();
|
||||
}
|
||||
|
||||
public override Quad SelectionQuad
|
||||
@ -103,14 +178,130 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
get
|
||||
{
|
||||
// correctly include the circle in the selection quad region, as it is usually outside the blueprint itself.
|
||||
var circleQuad = circle.ScreenSpaceDrawQuad;
|
||||
var actualQuad = ScreenSpaceDrawQuad;
|
||||
var leftQuad = circle.ScreenSpaceDrawQuad;
|
||||
var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad;
|
||||
|
||||
return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight),
|
||||
circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight));
|
||||
return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight),
|
||||
leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight));
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var baseBeatmap = createTestBeatmap();
|
||||
var baseBeatmap = CreateBeatmap();
|
||||
|
||||
BeatmapInfo = baseBeatmap.BeatmapInfo;
|
||||
ControlPointInfo = baseBeatmap.ControlPointInfo;
|
||||
@ -37,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual Beatmap CreateBeatmap() => createTestBeatmap();
|
||||
|
||||
private static Beatmap createTestBeatmap()
|
||||
{
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data)))
|
||||
|
Loading…
Reference in New Issue
Block a user