mirror of
https://github.com/ppy/osu.git
synced 2025-03-28 10:17:19 +08:00
Merge pull request #14742 from peppy/slider-timeline-velcotiy-adjust-v2
Add ability to view and adjust velocity and samples from the timeline
This commit is contained in:
commit
e2593570b7
@ -8,12 +8,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
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;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
@ -67,6 +69,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
public override void UpdateTimeAndPosition(SnapResult result)
|
public override void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
@ -75,6 +80,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
case SliderPlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
|
var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint;
|
||||||
|
|
||||||
|
HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint();
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1,27 +1,106 @@
|
|||||||
// 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.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class DifficultyPointPiece : TopPointPiece
|
public class DifficultyPointPiece : HitObjectPointPiece, IHasPopover
|
||||||
{
|
{
|
||||||
|
private readonly HitObject hitObject;
|
||||||
|
|
||||||
private readonly BindableNumber<double> speedMultiplier;
|
private readonly BindableNumber<double> speedMultiplier;
|
||||||
|
|
||||||
public DifficultyPointPiece(DifficultyControlPoint point)
|
public DifficultyPointPiece(HitObject hitObject)
|
||||||
: base(point)
|
: base(hitObject.DifficultyControlPoint)
|
||||||
{
|
{
|
||||||
speedMultiplier = point.SliderVelocityBindable.GetBoundCopy();
|
this.hitObject = hitObject;
|
||||||
|
|
||||||
Y = Height;
|
speedMultiplier = hitObject.DifficultyControlPoint.SliderVelocityBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true);
|
speedMultiplier.BindValueChanged(multiplier => Label.Text = $"{multiplier.NewValue:n2}x", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
this.ShowPopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new DifficultyEditPopover(hitObject);
|
||||||
|
|
||||||
|
public class DifficultyEditPopover : OsuPopover
|
||||||
|
{
|
||||||
|
private readonly HitObject hitObject;
|
||||||
|
private readonly DifficultyControlPoint point;
|
||||||
|
|
||||||
|
private SliderWithTextBoxInput<double> sliderVelocitySlider;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
|
||||||
|
public DifficultyEditPopover(HitObject hitObject)
|
||||||
|
{
|
||||||
|
this.hitObject = hitObject;
|
||||||
|
point = hitObject.DifficultyControlPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 200,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Velocity")
|
||||||
|
{
|
||||||
|
Current = new DifficultyControlPoint().SliderVelocityBindable,
|
||||||
|
KeyboardStep = 0.1f
|
||||||
|
},
|
||||||
|
new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectedPointBindable = point.SliderVelocityBindable;
|
||||||
|
|
||||||
|
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
||||||
|
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
||||||
|
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
||||||
|
double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
|
||||||
|
if (selectedPointBindable.Precision < expectedPrecision)
|
||||||
|
selectedPointBindable.Precision = expectedPrecision;
|
||||||
|
|
||||||
|
sliderVelocitySlider.Current = selectedPointBindable;
|
||||||
|
sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
|
{
|
||||||
|
public class HitObjectPointPiece : CircularContainer
|
||||||
|
{
|
||||||
|
private readonly ControlPoint point;
|
||||||
|
|
||||||
|
protected OsuSpriteText Label { get; private set; }
|
||||||
|
|
||||||
|
protected HitObjectPointPiece(ControlPoint point)
|
||||||
|
{
|
||||||
|
this.point = point;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
Color4 colour = point.GetRepresentingColour(colours);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Height = 16,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 8,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colour,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
Label = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding(5),
|
||||||
|
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
|
||||||
|
Colour = colours.B5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,88 +3,102 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
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.Cursor;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osuTK.Graphics;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class SamplePointPiece : CompositeDrawable
|
public class SamplePointPiece : HitObjectPointPiece, IHasPopover
|
||||||
{
|
{
|
||||||
private readonly SampleControlPoint samplePoint;
|
private readonly HitObject hitObject;
|
||||||
|
|
||||||
private readonly Bindable<string> bank;
|
private readonly Bindable<string> bank;
|
||||||
private readonly BindableNumber<int> volume;
|
private readonly BindableNumber<int> volume;
|
||||||
|
|
||||||
private OsuSpriteText text;
|
public SamplePointPiece(HitObject hitObject)
|
||||||
private Container volumeBox;
|
: base(hitObject.SampleControlPoint)
|
||||||
|
|
||||||
private const int max_volume_height = 22;
|
|
||||||
|
|
||||||
public SamplePointPiece(SampleControlPoint samplePoint)
|
|
||||||
{
|
{
|
||||||
this.samplePoint = samplePoint;
|
this.hitObject = hitObject;
|
||||||
volume = samplePoint.SampleVolumeBindable.GetBoundCopy();
|
volume = hitObject.SampleControlPoint.SampleVolumeBindable.GetBoundCopy();
|
||||||
bank = samplePoint.SampleBankBindable.GetBoundCopy();
|
bank = hitObject.SampleControlPoint.SampleBankBindable.GetBoundCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Vertical = 5 };
|
volume.BindValueChanged(volume => updateText());
|
||||||
|
bank.BindValueChanged(bank => updateText(), true);
|
||||||
|
}
|
||||||
|
|
||||||
Origin = Anchor.BottomCentre;
|
protected override bool OnClick(ClickEvent e)
|
||||||
Anchor = Anchor.BottomCentre;
|
{
|
||||||
|
this.ShowPopover();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
AutoSizeAxes = Axes.X;
|
private void updateText()
|
||||||
RelativeSizeAxes = Axes.Y;
|
{
|
||||||
|
Label.Text = $"{bank.Value} {volume.Value}";
|
||||||
|
}
|
||||||
|
|
||||||
Color4 colour = samplePoint.GetRepresentingColour(colours);
|
public Popover GetPopover() => new SampleEditPopover(hitObject);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
public class SampleEditPopover : OsuPopover
|
||||||
|
{
|
||||||
|
private readonly HitObject hitObject;
|
||||||
|
private readonly SampleControlPoint point;
|
||||||
|
|
||||||
|
private LabelledTextBox bank;
|
||||||
|
private SliderWithTextBoxInput<int> volume;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
|
||||||
|
public SampleEditPopover(HitObject hitObject)
|
||||||
{
|
{
|
||||||
volumeBox = new Circle
|
this.hitObject = hitObject;
|
||||||
|
point = hitObject.SampleControlPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
CornerRadius = 5,
|
new FillFlowContainer
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Y = -20,
|
|
||||||
Width = 10,
|
|
||||||
Colour = colour,
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
Height = 16,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 8,
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
Width = 200,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Colour = colour,
|
bank = new LabelledTextBox
|
||||||
RelativeSizeAxes = Axes.Both,
|
{
|
||||||
},
|
Label = "Bank Name",
|
||||||
text = new OsuSpriteText
|
},
|
||||||
{
|
volume = new SliderWithTextBoxInput<int>("Volume")
|
||||||
Anchor = Anchor.Centre,
|
{
|
||||||
Origin = Anchor.Centre,
|
Current = new SampleControlPoint().SampleVolumeBindable,
|
||||||
Padding = new MarginPadding(5),
|
}
|
||||||
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
|
|
||||||
Colour = colours.B5,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
volume.BindValueChanged(volume => volumeBox.Height = max_volume_height * volume.NewValue / 100f, true);
|
bank.Current = point.SampleBankBindable;
|
||||||
bank.BindValueChanged(bank => text.Text = bank.NewValue, true);
|
bank.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||||
|
|
||||||
|
volume.Current = point.SampleVolumeBindable;
|
||||||
|
volume.Current.BindValueChanged(_ => beatmap.Update(hitObject));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
private Track track;
|
private Track track;
|
||||||
|
|
||||||
private const float timeline_height = 72;
|
private const float timeline_height = 72;
|
||||||
private const float timeline_expanded_height = 156;
|
private const float timeline_expanded_height = 94;
|
||||||
|
|
||||||
public Timeline(Drawable userContent)
|
public Timeline(Drawable userContent)
|
||||||
{
|
{
|
||||||
@ -159,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (visible.NewValue)
|
if (visible.NewValue)
|
||||||
{
|
{
|
||||||
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
||||||
mainContent.MoveToY(36, 200, Easing.OutQuint);
|
mainContent.MoveToY(20, 200, Easing.OutQuint);
|
||||||
|
|
||||||
// delay the fade in else masking looks weird.
|
// delay the fade in else masking looks weird.
|
||||||
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
|
controlPoints.Delay(180).FadeIn(400, Easing.OutQuint);
|
||||||
|
@ -45,17 +45,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
switch (point)
|
switch (point)
|
||||||
{
|
{
|
||||||
case DifficultyControlPoint difficultyPoint:
|
|
||||||
AddInternal(new DifficultyPointPiece(difficultyPoint) { Depth = -2 });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TimingControlPoint timingPoint:
|
case TimingControlPoint timingPoint:
|
||||||
AddInternal(new TimingPointPiece(timingPoint));
|
AddInternal(new TimingPointPiece(timingPoint));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SampleControlPoint samplePoint:
|
|
||||||
AddInternal(new SamplePointPiece(samplePoint) { Depth = -1 });
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
@ -13,7 +13,9 @@ 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.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
@ -179,6 +181,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
colouredComponents.Colour = OsuColour.ForegroundTextColourFor(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SamplePointPiece sampleOverrideDisplay;
|
||||||
|
private DifficultyPointPiece difficultyOverrideDisplay;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap beatmap { get; set; }
|
||||||
|
|
||||||
|
private DifficultyControlPoint difficultyControlPoint;
|
||||||
|
private SampleControlPoint sampleControlPoint;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -194,6 +205,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
if (Item is IHasRepeats repeats)
|
if (Item is IHasRepeats repeats)
|
||||||
updateRepeats(repeats);
|
updateRepeats(repeats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (difficultyControlPoint != Item.DifficultyControlPoint)
|
||||||
|
{
|
||||||
|
difficultyControlPoint = Item.DifficultyControlPoint;
|
||||||
|
difficultyOverrideDisplay?.Expire();
|
||||||
|
|
||||||
|
if (Item.DifficultyControlPoint != null && Item is IHasDistance)
|
||||||
|
{
|
||||||
|
AddInternal(difficultyOverrideDisplay = new DifficultyPointPiece(Item)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopLeft,
|
||||||
|
Origin = Anchor.BottomCentre
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleControlPoint != Item.SampleControlPoint)
|
||||||
|
{
|
||||||
|
sampleControlPoint = Item.SampleControlPoint;
|
||||||
|
sampleOverrideDisplay?.Expire();
|
||||||
|
|
||||||
|
if (Item.SampleControlPoint != null)
|
||||||
|
{
|
||||||
|
AddInternal(sampleOverrideDisplay = new SamplePointPiece(Item)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.TopCentre
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRepeats(IHasRepeats repeats)
|
private void updateRepeats(IHasRepeats repeats)
|
||||||
@ -331,39 +372,66 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate dragOperation;
|
||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
base.OnDrag(e);
|
base.OnDrag(e);
|
||||||
|
|
||||||
OnDragHandled?.Invoke(e);
|
// schedule is temporary to ensure we don't process multiple times on a single update frame. we need to find a better method of doing this.
|
||||||
|
// without it, a hitobject's endtime may not always be in a valid state (ie. sliders, which needs to recompute their path).
|
||||||
if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time)
|
dragOperation?.Cancel();
|
||||||
|
dragOperation = Scheduler.Add(() =>
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
OnDragHandled?.Invoke(e);
|
||||||
|
|
||||||
|
if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time)
|
||||||
{
|
{
|
||||||
case IHasRepeats repeatHitObject:
|
switch (hitObject)
|
||||||
// find the number of repeats which can fit in the requested time.
|
{
|
||||||
var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1);
|
case IHasRepeats repeatHitObject:
|
||||||
var proposedCount = Math.Max(0, (int)Math.Round((time - hitObject.StartTime) / lengthOfOneRepeat) - 1);
|
double proposedDuration = time - hitObject.StartTime;
|
||||||
|
|
||||||
if (proposedCount == repeatHitObject.RepeatCount)
|
if (e.CurrentState.Keyboard.ShiftPressed)
|
||||||
return;
|
{
|
||||||
|
if (hitObject.DifficultyControlPoint == DifficultyControlPoint.DEFAULT)
|
||||||
|
hitObject.DifficultyControlPoint = new DifficultyControlPoint();
|
||||||
|
|
||||||
repeatHitObject.RepeatCount = proposedCount;
|
var newVelocity = hitObject.DifficultyControlPoint.SliderVelocity * (repeatHitObject.Duration / proposedDuration);
|
||||||
beatmap.Update(hitObject);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IHasDuration endTimeHitObject:
|
if (Precision.AlmostEquals(newVelocity, hitObject.DifficultyControlPoint.SliderVelocity))
|
||||||
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
return;
|
||||||
|
|
||||||
if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime)))
|
hitObject.DifficultyControlPoint.SliderVelocity = newVelocity;
|
||||||
return;
|
beatmap.Update(hitObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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)Math.Round(proposedDuration / lengthOfOneRepeat) - 1);
|
||||||
|
|
||||||
endTimeHitObject.Duration = snappedTime - hitObject.StartTime;
|
if (proposedCount == repeatHitObject.RepeatCount)
|
||||||
beatmap.Update(hitObject);
|
return;
|
||||||
break;
|
|
||||||
|
repeatHitObject.RepeatCount = proposedCount;
|
||||||
|
beatmap.Update(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IHasDuration endTimeHitObject:
|
||||||
|
var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time));
|
||||||
|
|
||||||
|
if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
endTimeHitObject.Duration = snappedTime - hitObject.StartTime;
|
||||||
|
beatmap.Update(hitObject);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -71,6 +72,31 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
PlayableBeatmap = playableBeatmap;
|
PlayableBeatmap = playableBeatmap;
|
||||||
|
|
||||||
|
// ensure we are not working with legacy control points.
|
||||||
|
// if we leave the legacy points around they will be applied over any local changes on
|
||||||
|
// ApplyDefaults calls. this should eventually be removed once the default logic is moved to the decoder/converter.
|
||||||
|
if (PlayableBeatmap.ControlPointInfo is LegacyControlPointInfo)
|
||||||
|
{
|
||||||
|
var newControlPoints = new ControlPointInfo();
|
||||||
|
|
||||||
|
foreach (var controlPoint in PlayableBeatmap.ControlPointInfo.AllControlPoints)
|
||||||
|
{
|
||||||
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case DifficultyControlPoint _:
|
||||||
|
case SampleControlPoint _:
|
||||||
|
// skip legacy types.
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
newControlPoints.Add(controlPoint.Time, controlPoint);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playableBeatmap.ControlPointInfo = newControlPoints;
|
||||||
|
}
|
||||||
|
|
||||||
this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo;
|
this.beatmapInfo = beatmapInfo ?? playableBeatmap.BeatmapInfo;
|
||||||
|
|
||||||
if (beatmapSkin is Skin skin)
|
if (beatmapSkin is Skin skin)
|
||||||
|
@ -25,7 +25,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public double TrackLength => track.Value?.Length ?? 60000;
|
public double TrackLength => track.Value?.Length ?? 60000;
|
||||||
|
|
||||||
public ControlPointInfo ControlPointInfo;
|
public ControlPointInfo ControlPointInfo => Beatmap.ControlPointInfo;
|
||||||
|
|
||||||
|
public IBeatmap Beatmap { get; set; }
|
||||||
|
|
||||||
private readonly BindableBeatDivisor beatDivisor;
|
private readonly BindableBeatDivisor beatDivisor;
|
||||||
|
|
||||||
@ -42,25 +44,15 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSeeking { get; private set; }
|
public bool IsSeeking { get; private set; }
|
||||||
|
|
||||||
public EditorClock(IBeatmap beatmap, BindableBeatDivisor beatDivisor)
|
public EditorClock(IBeatmap beatmap = null, BindableBeatDivisor beatDivisor = null)
|
||||||
: this(beatmap.ControlPointInfo, beatDivisor)
|
|
||||||
{
|
{
|
||||||
}
|
Beatmap = beatmap ?? new Beatmap();
|
||||||
|
|
||||||
public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor)
|
this.beatDivisor = beatDivisor ?? new BindableBeatDivisor();
|
||||||
{
|
|
||||||
this.beatDivisor = beatDivisor;
|
|
||||||
|
|
||||||
ControlPointInfo = controlPointInfo;
|
|
||||||
|
|
||||||
underlyingClock = new DecoupleableInterpolatingFramedClock();
|
underlyingClock = new DecoupleableInterpolatingFramedClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EditorClock()
|
|
||||||
: this(new ControlPointInfo(), new BindableBeatDivisor())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seek to the closest snappable beat from a time.
|
/// Seek to the closest snappable beat from a time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -12,8 +12,6 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
new GroupSection(),
|
new GroupSection(),
|
||||||
new TimingSection(),
|
new TimingSection(),
|
||||||
new DifficultySection(),
|
|
||||||
new SampleSection(),
|
|
||||||
new EffectSection(),
|
new EffectSection(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Beatmaps.Legacy;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
|
||||||
{
|
|
||||||
internal class DifficultySection : Section<DifficultyControlPoint>
|
|
||||||
{
|
|
||||||
private SliderWithTextBoxInput<double> sliderVelocitySlider;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Flow.AddRange(new[]
|
|
||||||
{
|
|
||||||
sliderVelocitySlider = new SliderWithTextBoxInput<double>("Slider Velocity")
|
|
||||||
{
|
|
||||||
Current = new DifficultyControlPoint().SliderVelocityBindable,
|
|
||||||
KeyboardStep = 0.1f
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnControlPointChanged(ValueChangedEvent<DifficultyControlPoint> point)
|
|
||||||
{
|
|
||||||
if (point.NewValue != null)
|
|
||||||
{
|
|
||||||
var selectedPointBindable = point.NewValue.SliderVelocityBindable;
|
|
||||||
|
|
||||||
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
|
|
||||||
// generally that level of precision could only be set by externally editing the .osu file, so at the point
|
|
||||||
// a user is looking to update this within the editor it should be safe to obliterate this additional precision.
|
|
||||||
double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
|
|
||||||
if (selectedPointBindable.Precision < expectedPrecision)
|
|
||||||
selectedPointBindable.Precision = expectedPrecision;
|
|
||||||
|
|
||||||
sliderVelocitySlider.Current = selectedPointBindable;
|
|
||||||
sliderVelocitySlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override DifficultyControlPoint CreatePoint()
|
|
||||||
{
|
|
||||||
var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(SelectedGroup.Value.Time) ?? DifficultyControlPoint.DEFAULT;
|
|
||||||
|
|
||||||
return new DifficultyControlPoint
|
|
||||||
{
|
|
||||||
SliderVelocity = reference.SliderVelocity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Beatmaps.Legacy;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
|
||||||
{
|
|
||||||
internal class SampleSection : Section<SampleControlPoint>
|
|
||||||
{
|
|
||||||
private LabelledTextBox bank;
|
|
||||||
private SliderWithTextBoxInput<int> volume;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
Flow.AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
bank = new LabelledTextBox
|
|
||||||
{
|
|
||||||
Label = "Bank Name",
|
|
||||||
},
|
|
||||||
volume = new SliderWithTextBoxInput<int>("Volume")
|
|
||||||
{
|
|
||||||
Current = new SampleControlPoint().SampleVolumeBindable,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnControlPointChanged(ValueChangedEvent<SampleControlPoint> point)
|
|
||||||
{
|
|
||||||
if (point.NewValue != null)
|
|
||||||
{
|
|
||||||
bank.Current = point.NewValue.SampleBankBindable;
|
|
||||||
bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
|
||||||
|
|
||||||
volume.Current = point.NewValue.SampleVolumeBindable;
|
|
||||||
volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override SampleControlPoint CreatePoint()
|
|
||||||
{
|
|
||||||
var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(SelectedGroup.Value.Time) ?? SampleControlPoint.DEFAULT;
|
|
||||||
|
|
||||||
return new SampleControlPoint
|
|
||||||
{
|
|
||||||
SampleBank = reference.SampleBank,
|
|
||||||
SampleVolume = reference.SampleVolume,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
@ -23,7 +22,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected EditorClockTestScene()
|
protected EditorClockTestScene()
|
||||||
{
|
{
|
||||||
Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false };
|
Clock = new EditorClock(new Beatmap(), BeatDivisor) { IsCoupled = false };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
|
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
|
||||||
{
|
{
|
||||||
Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo;
|
Clock.Beatmap = e.NewValue.Beatmap;
|
||||||
Clock.ChangeSource(e.NewValue.Track);
|
Clock.ChangeSource(e.NewValue.Track);
|
||||||
Clock.ProcessFrame();
|
Clock.ProcessFrame();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user