mirror of
https://github.com/ppy/osu
synced 2025-01-10 08:09:40 +00:00
Merge pull request #23405 from peppy/slider-velocity-inspector
Show beatmap slider velocity statistics when adjusting velocity
This commit is contained in:
commit
8c3b1faa01
@ -1,152 +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 System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Screens.Edit;
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
internal partial class HitObjectInspector : CompositeDrawable
|
||||
{
|
||||
private OsuTextFlowContainer inspectorText = null!;
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
InternalChild = inspectorText = new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||
updateInspectorText();
|
||||
}
|
||||
|
||||
private ScheduledDelegate? rollingTextUpdate;
|
||||
|
||||
private void updateInspectorText()
|
||||
{
|
||||
inspectorText.Clear();
|
||||
rollingTextUpdate?.Cancel();
|
||||
rollingTextUpdate = null;
|
||||
|
||||
switch (EditorBeatmap.SelectedHitObjects.Count)
|
||||
{
|
||||
case 0:
|
||||
addValue("No selection");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
||||
|
||||
addHeader("Type");
|
||||
addValue($"{selected.GetType().ReadableName()}");
|
||||
|
||||
addHeader("Time");
|
||||
addValue($"{selected.StartTime:#,0.##}ms");
|
||||
|
||||
switch (selected)
|
||||
{
|
||||
case IHasPosition pos:
|
||||
addHeader("Position");
|
||||
addValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
|
||||
break;
|
||||
|
||||
case IHasXPosition x:
|
||||
addHeader("Position");
|
||||
|
||||
addValue($"x:{x.X:#,0.##} ");
|
||||
break;
|
||||
|
||||
case IHasYPosition y:
|
||||
addHeader("Position");
|
||||
|
||||
addValue($"y:{y.Y:#,0.##}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (selected is IHasDistance distance)
|
||||
{
|
||||
addHeader("Distance");
|
||||
addValue($"{distance.Distance:#,0.##}px");
|
||||
}
|
||||
|
||||
if (selected is IHasSliderVelocity sliderVelocity)
|
||||
{
|
||||
addHeader("Slider Velocity");
|
||||
addValue($"{sliderVelocity.SliderVelocity:#,0.00}x");
|
||||
}
|
||||
|
||||
if (selected is IHasRepeats repeats)
|
||||
{
|
||||
addHeader("Repeats");
|
||||
addValue($"{repeats.RepeatCount:#,0.##}");
|
||||
}
|
||||
|
||||
if (selected is IHasDuration duration)
|
||||
{
|
||||
addHeader("End Time");
|
||||
addValue($"{duration.EndTime:#,0.##}ms");
|
||||
addHeader("Duration");
|
||||
addValue($"{duration.Duration:#,0.##}ms");
|
||||
}
|
||||
|
||||
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
||||
// This is a good middle-ground for the time being.
|
||||
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
||||
break;
|
||||
|
||||
default:
|
||||
addHeader("Selected Objects");
|
||||
addValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
||||
|
||||
addHeader("Start Time");
|
||||
addValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
||||
|
||||
addHeader("End Time");
|
||||
addValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||
break;
|
||||
}
|
||||
|
||||
void addHeader(string header) => inspectorText.AddParagraph($"{header}: ", s =>
|
||||
{
|
||||
s.Padding = new MarginPadding { Top = 2 };
|
||||
s.Font = s.Font.With(size: 12);
|
||||
s.Colour = colourProvider.Content2;
|
||||
});
|
||||
|
||||
void addValue(string value) => inspectorText.AddParagraph(value, s =>
|
||||
{
|
||||
s.Font = s.Font.With(weight: FontWeight.SemiBold);
|
||||
s.Colour = colourProvider.Content1;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
49
osu.Game/Screens/Edit/Compose/Components/EditorInspector.cs
Normal file
49
osu.Game/Screens/Edit/Compose/Components/EditorInspector.cs
Normal file
@ -0,0 +1,49 @@
|
||||
// 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.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
internal partial class EditorInspector : CompositeDrawable
|
||||
{
|
||||
protected OsuTextFlowContainer InspectorText = null!;
|
||||
|
||||
[Resolved]
|
||||
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
InternalChild = InspectorText = new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
}
|
||||
|
||||
protected void AddHeader(string header) => InspectorText.AddParagraph($"{header}: ", s =>
|
||||
{
|
||||
s.Padding = new MarginPadding { Top = 2 };
|
||||
s.Font = s.Font.With(size: 12);
|
||||
s.Colour = colourProvider.Content2;
|
||||
});
|
||||
|
||||
protected void AddValue(string value) => InspectorText.AddParagraph(value, s =>
|
||||
{
|
||||
s.Font = s.Font.With(weight: FontWeight.SemiBold);
|
||||
s.Colour = colourProvider.Content1;
|
||||
});
|
||||
}
|
||||
}
|
111
osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
Normal file
111
osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
Normal file
@ -0,0 +1,111 @@
|
||||
// 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.Extensions.TypeExtensions;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
internal partial class HitObjectInspector : EditorInspector
|
||||
{
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||
updateInspectorText();
|
||||
}
|
||||
|
||||
private ScheduledDelegate? rollingTextUpdate;
|
||||
|
||||
private void updateInspectorText()
|
||||
{
|
||||
InspectorText.Clear();
|
||||
rollingTextUpdate?.Cancel();
|
||||
rollingTextUpdate = null;
|
||||
|
||||
switch (EditorBeatmap.SelectedHitObjects.Count)
|
||||
{
|
||||
case 0:
|
||||
AddValue("No selection");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
||||
|
||||
AddHeader("Type");
|
||||
AddValue($"{selected.GetType().ReadableName()}");
|
||||
|
||||
AddHeader("Time");
|
||||
AddValue($"{selected.StartTime:#,0.##}ms");
|
||||
|
||||
switch (selected)
|
||||
{
|
||||
case IHasPosition pos:
|
||||
AddHeader("Position");
|
||||
AddValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
|
||||
break;
|
||||
|
||||
case IHasXPosition x:
|
||||
AddHeader("Position");
|
||||
|
||||
AddValue($"x:{x.X:#,0.##} ");
|
||||
break;
|
||||
|
||||
case IHasYPosition y:
|
||||
AddHeader("Position");
|
||||
|
||||
AddValue($"y:{y.Y:#,0.##}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (selected is IHasDistance distance)
|
||||
{
|
||||
AddHeader("Distance");
|
||||
AddValue($"{distance.Distance:#,0.##}px");
|
||||
}
|
||||
|
||||
if (selected is IHasSliderVelocity sliderVelocity)
|
||||
{
|
||||
AddHeader("Slider Velocity");
|
||||
AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x");
|
||||
}
|
||||
|
||||
if (selected is IHasRepeats repeats)
|
||||
{
|
||||
AddHeader("Repeats");
|
||||
AddValue($"{repeats.RepeatCount:#,0.##}");
|
||||
}
|
||||
|
||||
if (selected is IHasDuration duration)
|
||||
{
|
||||
AddHeader("End Time");
|
||||
AddValue($"{duration.EndTime:#,0.##}ms");
|
||||
AddHeader("Duration");
|
||||
AddValue($"{duration.Duration:#,0.##}ms");
|
||||
}
|
||||
|
||||
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
||||
// This is a good middle-ground for the time being.
|
||||
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
||||
break;
|
||||
|
||||
default:
|
||||
AddHeader("Selected Objects");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
||||
|
||||
AddHeader("Start Time");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
||||
|
||||
AddHeader("End Time");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -95,7 +95,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
||||
}
|
||||
},
|
||||
new SliderVelocityInspector(),
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -105,7 +106,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray();
|
||||
|
||||
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
||||
var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1 ? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable : null;
|
||||
var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1
|
||||
? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable
|
||||
: null;
|
||||
|
||||
if (selectedPointBindable != null)
|
||||
{
|
||||
@ -139,4 +142,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class SliderVelocityInspector : EditorInspector
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||
updateInspectorText();
|
||||
}
|
||||
|
||||
private void updateInspectorText()
|
||||
{
|
||||
InspectorText.Clear();
|
||||
|
||||
double[] sliderVelocities = EditorBeatmap.HitObjects.OfType<IHasSliderVelocity>().Select(sv => sv.SliderVelocity).OrderBy(v => v).ToArray();
|
||||
|
||||
if (sliderVelocities.Length < 2)
|
||||
return;
|
||||
|
||||
double? modeSliderVelocity = sliderVelocities.GroupBy(v => v).MaxBy(v => v.Count())?.Key;
|
||||
double? medianSliderVelocity = sliderVelocities[sliderVelocities.Length / 2];
|
||||
|
||||
AddHeader("Average velocity");
|
||||
AddValue($"{medianSliderVelocity:#,0.00}x");
|
||||
|
||||
AddHeader("Most used velocity");
|
||||
AddValue($"{modeSliderVelocity:#,0.00}x");
|
||||
|
||||
AddHeader("Velocity range");
|
||||
AddValue($"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x");
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
EditorBeatmap.TransactionBegan -= updateInspectorText;
|
||||
EditorBeatmap.TransactionEnded -= updateInspectorText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user