Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hit object inspector to editor #23133

Merged
merged 12 commits into from Apr 10, 2023
1 change: 0 additions & 1 deletion osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
Expand Up @@ -48,7 +48,6 @@ public CatchHitObjectComposer(CatchRuleset ruleset)
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
Expand Down
57 changes: 18 additions & 39 deletions osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
Expand Up @@ -11,8 +11,6 @@
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;
Expand Down Expand Up @@ -47,8 +45,6 @@ public abstract partial class DistancedHitObjectComposer<TObject> : HitObjectCom

IBindable<double> IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier;

protected ExpandingToolboxContainer RightSideToolboxContainer { get; private set; }

private ExpandableSlider<double, SizeSlider<double>> distanceSpacingSlider;
private ExpandableButton currentDistanceSpacingButton;

Expand All @@ -67,47 +63,29 @@ protected DistancedHitObjectComposer(Ruleset ruleset)
[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();

Debug.Assert(objects != null);

DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
DistanceSnapToggle.Value = TernaryState.True;
},
RelativeSizeAxes = Axes.X,
}
}
}
(HitObject before, HitObject after)? objects = getObjectsOnEitherSideOfCurrentTime();

Debug.Assert(objects != null);

DistanceSpacingMultiplier.Value = ReadCurrentDistanceSnap(objects.Value.before, objects.Value.after);
DistanceSnapToggle.Value = TernaryState.True;
},
RelativeSizeAxes = Axes.X,
}
}
});
Expand Down Expand Up @@ -261,7 +239,8 @@ protected virtual bool AdjustDistanceSpacing(GlobalAction action, float amount)

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)
Expand Down
33 changes: 31 additions & 2 deletions osu.Game/Rulesets/Edit/HitObjectComposer.cs
Expand Up @@ -58,8 +58,15 @@ public abstract partial class HitObjectComposer<TObject> : HitObjectComposer, IP
[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 };
Expand All @@ -82,7 +89,7 @@ protected HitObjectComposer(Ruleset ruleset)
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);

Expand Down Expand Up @@ -131,7 +138,7 @@ private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
},
new ExpandingToolboxContainer(60, 200)
LeftToolbox = new ExpandingToolboxContainer(60, 200)
{
Children = new Drawable[]
{
Expand All @@ -153,6 +160,28 @@ private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
},
}
},
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
Expand Down
146 changes: 146 additions & 0 deletions osu.Game/Rulesets/Edit/HitObjectInspector.cs
@@ -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;
});
}
}
}