mirror of
https://github.com/ppy/osu.git
synced 2024-12-17 06:02:55 +08:00
Merge pull request #26313 from OliBomby/grids-4
Add grid placement tool
This commit is contained in:
commit
bfad281f62
126
osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
Normal file
126
osu.Game.Rulesets.Osu/Edit/Blueprints/GridPlacementBlueprint.cs
Normal file
@ -0,0 +1,126 @@
|
||||
// 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.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
{
|
||||
public partial class GridPlacementBlueprint : PlacementBlueprint
|
||||
{
|
||||
[Resolved]
|
||||
private HitObjectComposer? hitObjectComposer { get; set; }
|
||||
|
||||
private OsuGridToolboxGroup gridToolboxGroup = null!;
|
||||
private Vector2 originalOrigin;
|
||||
private float originalSpacing;
|
||||
private float originalRotation;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGridToolboxGroup gridToolboxGroup)
|
||||
{
|
||||
this.gridToolboxGroup = gridToolboxGroup;
|
||||
originalOrigin = gridToolboxGroup.StartPosition.Value;
|
||||
originalSpacing = gridToolboxGroup.Spacing.Value;
|
||||
originalRotation = gridToolboxGroup.GridLinesRotation.Value;
|
||||
}
|
||||
|
||||
public override void EndPlacement(bool commit)
|
||||
{
|
||||
if (!commit && PlacementActive != PlacementState.Finished)
|
||||
{
|
||||
gridToolboxGroup.StartPosition.Value = originalOrigin;
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
|
||||
base.EndPlacement(commit);
|
||||
|
||||
// You typically only place the grid once, so we switch back to the last tool after placement.
|
||||
if (commit && hitObjectComposer is OsuHitObjectComposer osuHitObjectComposer)
|
||||
osuHitObjectComposer.SetLastTool();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
switch (PlacementActive)
|
||||
{
|
||||
case PlacementState.Waiting:
|
||||
BeginPlacement(true);
|
||||
return true;
|
||||
|
||||
case PlacementState.Active:
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Right)
|
||||
{
|
||||
// Reset the grid to the default values.
|
||||
gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default;
|
||||
gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
BeginPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnDragStart(e);
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
if (PlacementActive == PlacementState.Active)
|
||||
EndPlacement(true);
|
||||
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
public override SnapType SnapType => ~SnapType.GlobalGrids;
|
||||
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
{
|
||||
var pos = ToLocalSpace(result.ScreenSpacePosition);
|
||||
|
||||
if (PlacementActive != PlacementState.Active)
|
||||
gridToolboxGroup.StartPosition.Value = pos;
|
||||
else
|
||||
{
|
||||
// Default to the original spacing and rotation if the distance is too small.
|
||||
if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
|
||||
{
|
||||
gridToolboxGroup.Spacing.Value = originalSpacing;
|
||||
if (!gridToolboxGroup.GridLinesRotation.Disabled)
|
||||
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
gridToolboxGroup.SetGridFromPoints(gridToolboxGroup.StartPosition.Value, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
Normal file
29
osu.Game.Rulesets.Osu/Edit/GridFromPointsTool.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Tools;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class GridFromPointsTool : CompositionTool
|
||||
{
|
||||
public GridFromPointsTool()
|
||||
: base("Grid")
|
||||
{
|
||||
TooltipText = """
|
||||
Left click to set the origin.
|
||||
Left click again to set the spacing and rotation.
|
||||
Right click to reset to default.
|
||||
Click and drag to set the origin, spacing and rotation.
|
||||
""";
|
||||
}
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.DraftingCompass };
|
||||
|
||||
public override PlacementBlueprint CreatePlacementBlueprint() => new GridPlacementBlueprint();
|
||||
}
|
||||
}
|
@ -11,12 +11,10 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||
@ -40,7 +38,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.X,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -50,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 0f,
|
||||
MaxValue = OsuPlayfield.BASE_SIZE.Y,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -60,7 +56,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = 4f,
|
||||
MaxValue = 128f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -70,14 +65,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
MinValue = -180f,
|
||||
MaxValue = 180f,
|
||||
Precision = 1f
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Read-only bindable representing the grid's origin.
|
||||
/// Equivalent to <code>new Vector2(StartPositionX, StartPositionY)</code>
|
||||
/// </summary>
|
||||
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> StartPosition { get; } = new Bindable<Vector2>(OsuPlayfield.BASE_SIZE / 2);
|
||||
|
||||
/// <summary>
|
||||
/// Read-only bindable representing the grid's spacing in both the X and Y dimension.
|
||||
@ -93,8 +87,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private ExpandableSlider<float> gridLinesRotationSlider = null!;
|
||||
private EditorRadioButtonCollection gridTypeButtons = null!;
|
||||
|
||||
private ExpandableButton useSelectedObjectPositionButton = null!;
|
||||
|
||||
public OsuGridToolboxGroup()
|
||||
: base("grid")
|
||||
{
|
||||
@ -102,6 +94,26 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private const float max_automatic_spacing = 64;
|
||||
|
||||
public void SetGridFromPoints(Vector2 point1, Vector2 point2)
|
||||
{
|
||||
StartPositionX.Value = point1.X;
|
||||
StartPositionY.Value = point1.Y;
|
||||
|
||||
// Get the angle between the two points and normalize to the valid range.
|
||||
if (!GridLinesRotation.Disabled)
|
||||
{
|
||||
float period = GridLinesRotation.MaxValue - GridLinesRotation.MinValue;
|
||||
GridLinesRotation.Value = normalizeRotation(MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X)), period);
|
||||
}
|
||||
|
||||
// Divide the distance so that there is a good density of grid lines.
|
||||
// This matches the maximum grid size of the grid size cycling hotkey.
|
||||
float dist = Vector2.Distance(point1, point2);
|
||||
while (dist >= max_automatic_spacing)
|
||||
dist /= 2;
|
||||
Spacing.Value = dist;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -117,20 +129,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Current = StartPositionY,
|
||||
KeyboardStep = 1,
|
||||
},
|
||||
useSelectedObjectPositionButton = new ExpandableButton
|
||||
{
|
||||
ExpandedLabelText = "Centre on selected object",
|
||||
Action = () =>
|
||||
{
|
||||
if (editorBeatmap.SelectedHitObjects.Count != 1)
|
||||
return;
|
||||
|
||||
var position = ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position;
|
||||
StartPosition.Value = new Vector2(MathF.Round(position.X), MathF.Round(position.Y));
|
||||
updateEnabledStates();
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
spacingSlider = new ExpandableSlider<float>
|
||||
{
|
||||
Current = Spacing,
|
||||
@ -179,15 +177,15 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
StartPositionX.BindValueChanged(x =>
|
||||
{
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}";
|
||||
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}";
|
||||
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
|
||||
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
|
||||
}, true);
|
||||
|
||||
StartPositionY.BindValueChanged(y =>
|
||||
{
|
||||
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}";
|
||||
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}";
|
||||
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
|
||||
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
|
||||
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
|
||||
}, true);
|
||||
|
||||
@ -195,13 +193,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
StartPositionX.Value = pos.NewValue.X;
|
||||
StartPositionY.Value = pos.NewValue.Y;
|
||||
updateEnabledStates();
|
||||
});
|
||||
|
||||
Spacing.BindValueChanged(spacing =>
|
||||
{
|
||||
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}";
|
||||
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}";
|
||||
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
|
||||
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
|
||||
SpacingVector.Value = new Vector2(spacing.NewValue);
|
||||
editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue;
|
||||
}, true);
|
||||
@ -219,34 +216,29 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
switch (v.NewValue)
|
||||
{
|
||||
case PositionSnapGridType.Square:
|
||||
GridLinesRotation.Value = ((GridLinesRotation.Value + 405) % 90) - 45;
|
||||
GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 90);
|
||||
GridLinesRotation.MinValue = -45;
|
||||
GridLinesRotation.MaxValue = 45;
|
||||
break;
|
||||
|
||||
case PositionSnapGridType.Triangle:
|
||||
GridLinesRotation.Value = ((GridLinesRotation.Value + 390) % 60) - 30;
|
||||
GridLinesRotation.Value = normalizeRotation(GridLinesRotation.Value, 60);
|
||||
GridLinesRotation.MinValue = -30;
|
||||
GridLinesRotation.MaxValue = 30;
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
|
||||
editorBeatmap.BeatmapReprocessed += updateEnabledStates;
|
||||
editorBeatmap.SelectedHitObjects.BindCollectionChanged((_, _) => updateEnabledStates());
|
||||
expandingContainer?.Expanded.BindValueChanged(v =>
|
||||
{
|
||||
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
|
||||
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
|
||||
updateEnabledStates();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updateEnabledStates()
|
||||
private float normalizeRotation(float rotation, float period)
|
||||
{
|
||||
useSelectedObjectPositionButton.Enabled.Value = expandingContainer?.Expanded.Value == true
|
||||
&& editorBeatmap.SelectedHitObjects.Count == 1
|
||||
&& !Precision.AlmostEquals(StartPosition.Value, ((IHasPosition)editorBeatmap.SelectedHitObjects.Single()).Position, 0.5f);
|
||||
return ((rotation + 360 + period * 0.5f) % period) - period * 0.5f;
|
||||
}
|
||||
|
||||
private void nextGridSize()
|
||||
|
@ -45,7 +45,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
new HitCircleCompositionTool(),
|
||||
new SliderCompositionTool(),
|
||||
new SpinnerCompositionTool()
|
||||
new SpinnerCompositionTool(),
|
||||
new GridFromPointsTool()
|
||||
};
|
||||
|
||||
private readonly Bindable<TernaryState> rectangularGridSnapToggle = new Bindable<TernaryState>();
|
||||
@ -79,13 +80,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
// Give a bit of breathing room around the playfield content.
|
||||
PlayfieldContentContainer.Padding = new MarginPadding(10);
|
||||
|
||||
LayerBelowRuleset.AddRange(new Drawable[]
|
||||
{
|
||||
LayerBelowRuleset.Add(
|
||||
distanceSnapGridContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
|
||||
selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid();
|
||||
|
@ -20,13 +20,13 @@ namespace osu.Game.Overlays.Settings
|
||||
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS };
|
||||
}
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
|
||||
public IEnumerable<string> Keywords { get; set; } = Array.Empty<string>();
|
||||
|
||||
public BindableBool CanBeShown { get; } = new BindableBool(true);
|
||||
IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
|
||||
public override IEnumerable<LocalisableString> FilterTerms
|
||||
{
|
||||
get
|
||||
|
@ -90,6 +90,9 @@ namespace osu.Game.Rulesets.Edit
|
||||
private Bindable<bool> autoSeekOnPlacement;
|
||||
private readonly Bindable<bool> composerFocusMode = new Bindable<bool>();
|
||||
|
||||
[CanBeNull]
|
||||
private RadioButton lastTool;
|
||||
|
||||
protected DrawableRuleset<TObject> DrawableRuleset { get; private set; }
|
||||
|
||||
protected HitObjectComposer(Ruleset ruleset)
|
||||
@ -213,8 +216,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
},
|
||||
};
|
||||
|
||||
toolboxCollection.Items = CompositionTools
|
||||
.Prepend(new SelectTool())
|
||||
toolboxCollection.Items = (CompositionTools.Prepend(new SelectTool()))
|
||||
.Select(t => new HitObjectCompositionToolButton(t, () => toolSelected(t)))
|
||||
.ToList();
|
||||
|
||||
@ -231,7 +233,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Select(b => new DrawableTernaryButton(b)));
|
||||
|
||||
setSelectTool();
|
||||
SetSelectTool();
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
|
||||
}
|
||||
@ -256,7 +258,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
// it's important this is performed before the similar code in EditorRadioButton disables the button.
|
||||
if (!timing.NewValue)
|
||||
setSelectTool();
|
||||
SetSelectTool();
|
||||
});
|
||||
|
||||
EditorBeatmap.HasTiming.BindValueChanged(hasTiming =>
|
||||
@ -460,14 +462,18 @@ namespace osu.Game.Rulesets.Edit
|
||||
if (EditorBeatmap.SelectedHitObjects.Any())
|
||||
{
|
||||
// ensure in selection mode if a selection is made.
|
||||
setSelectTool();
|
||||
SetSelectTool();
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelectTool() => toolboxCollection.Items.First().Select();
|
||||
public void SetSelectTool() => toolboxCollection.Items.First().Select();
|
||||
|
||||
public void SetLastTool() => (lastTool ?? toolboxCollection.Items.First()).Select();
|
||||
|
||||
private void toolSelected(CompositionTool tool)
|
||||
{
|
||||
lastTool = toolboxCollection.Items.OfType<HitObjectCompositionToolButton>().FirstOrDefault(i => i.Tool == BlueprintContainer.CurrentTool);
|
||||
|
||||
BlueprintContainer.CurrentTool = tool;
|
||||
|
||||
if (!(tool is SelectTool))
|
||||
|
@ -71,6 +71,11 @@ namespace osu.Game.Rulesets.Edit
|
||||
PlacementActive = PlacementState.Finished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines which objects to snap to for the snap result in <see cref="UpdateTimeAndPosition"/>.
|
||||
/// </summary>
|
||||
public virtual SnapType SnapType => SnapType.All;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the time and position of this <see cref="PlacementBlueprint"/> based on the provided snap information.
|
||||
/// </summary>
|
||||
|
@ -297,7 +297,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private void updatePlacementPosition()
|
||||
{
|
||||
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position);
|
||||
var snapResult = Composer.FindSnappedPositionAndTime(InputManager.CurrentState.Mouse.Position, CurrentPlacement.SnapType);
|
||||
|
||||
// if no time was found from positional snapping, we should still quantize to the beat.
|
||||
snapResult.Time ??= Beatmap.SnapTime(EditorClock.CurrentTime, null);
|
||||
|
Loading…
Reference in New Issue
Block a user