Merge pull request #23133 from peppy/editor-inspector

Add hit object inspector to editor
This commit is contained in:
Bartłomiej Dach 2023-04-10 15:13:33 +02:00 committed by GitHub
commit 1255d489fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 40 deletions

View File

@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Edit
private void load()
{
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
RightSideToolboxContainer.Alpha = 0;
DistanceSpacingMultiplier.Disabled = true;
LayerBelowRuleset.Add(new PlayfieldBorder

View File

@ -11,8 +11,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@ -47,8 +45,6 @@ namespace osu.Game.Rulesets.Edit
IBindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;
protected ExpandingToolboxContainer RightSideToolboxContainer { get; private set; }
private ExpandableSlider<double, SizeSlider<double>> distanceSpacingSlider;
private ExpandableButton currentDistanceSpacingButton;
@ -67,47 +63,29 @@ namespace osu.Game.Rulesets.Edit
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
AddInternal(new Container
RightToolbox.Add(new EditorToolboxGroup("snapping")
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
Children = new Drawable[]
{
new Box
distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>>
{
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
KeyboardStep = adjust_step,
// Manual binding in LoadComplete to handle one-way event flow.
Current = DistanceSpacingMultiplier.GetUnboundCopy(),
},
RightSideToolboxContainer = new ExpandingToolboxContainer(130, 250)
currentDistanceSpacingButton = new ExpandableButton
{
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
Child = new EditorToolboxGroup("snapping")
Action = () =>
{
Children = new Drawable[]
{
distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>>
{
KeyboardStep = adjust_step,
// Manual binding in LoadComplete to handle one-way event flow.
Current = DistanceSpacingMultiplier.GetUnboundCopy(),
},
currentDistanceSpacingButton = new ExpandableButton
{
Action = () =>
{
(HitObject before, HitObject after)? objects = getObjectsOnEitherSideOfCurrentTime();
(HitObject before, HitObject after)? objects = getObjectsOnEitherSideOfCurrentTime();
Debug.Assert(objects != null);
Debug.Assert(objects != null);
DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
DistanceSnapToggle.Value = TernaryState.True;
},
RelativeSizeAxes = Axes.X,
}
}
}
DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
DistanceSnapToggle.Value = TernaryState.True;
},
RelativeSizeAxes = Axes.X,
}
}
});
@ -261,7 +239,8 @@ namespace osu.Game.Rulesets.Edit
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true)
{
return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor);
return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1
/ BeatSnapProvider.BeatDivisor);
}
public virtual float DurationToDistance(HitObject referenceObject, double duration)

View File

@ -58,8 +58,15 @@ namespace osu.Game.Rulesets.Edit
[Resolved]
protected IBeatSnapProvider BeatSnapProvider { get; private set; }
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
protected ComposeBlueprintContainer BlueprintContainer { get; private set; }
protected ExpandingToolboxContainer LeftToolbox { get; private set; }
protected ExpandingToolboxContainer RightToolbox { get; private set; }
private DrawableEditorRulesetWrapper<TObject> drawableRulesetWrapper;
protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both };
@ -82,7 +89,7 @@ namespace osu.Game.Rulesets.Edit
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
private void load(OsuConfigManager config)
{
autoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
@ -131,7 +138,7 @@ namespace osu.Game.Rulesets.Edit
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
},
new ExpandingToolboxContainer(60, 200)
LeftToolbox = new ExpandingToolboxContainer(60, 200)
{
Children = new Drawable[]
{
@ -153,6 +160,28 @@ namespace osu.Game.Rulesets.Edit
},
}
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new Box
{
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
},
RightToolbox = new ExpandingToolboxContainer(130, 250)
{
Child = new EditorToolboxGroup("inspector")
{
Child = new HitObjectInspector()
},
}
}
}
};
toolboxCollection.Items = CompositionTools

View File

@ -0,0 +1,146 @@
// 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 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;
});
}
}
}