mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 05:22:54 +08:00
5c8ae6f851
As I look into re-implementing the ability to choose combo colour for an object (also known as "colourhax") from the editor UI, I stumble upon these wretched ternary items again and sigh a deep sigh of annoyance. The structure is overly rigid. `TernaryItem` does nothing that `DrawableTernaryItem` couldn't, except make it more annoying to add specific sub-variants of `DrawableTernaryItem` that could do more things. Yes you could sprinkle more levels of virtuals to `CreateDrawableButton()` or something, but after all, as Saint Exupéry says, "perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away." So I'm leaning for taking one step towards perfection.
234 lines
8.5 KiB
C#
234 lines
8.5 KiB
C#
// 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.Collections.Generic;
|
|
using System.Linq;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Input.Bindings;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Input.Bindings;
|
|
using osu.Game.Rulesets.Catch.Objects;
|
|
using osu.Game.Rulesets.Catch.UI;
|
|
using osu.Game.Rulesets.Edit;
|
|
using osu.Game.Rulesets.Edit.Tools;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Rulesets.Objects;
|
|
using osu.Game.Rulesets.UI;
|
|
using osu.Game.Screens.Edit.Components.TernaryButtons;
|
|
using osu.Game.Screens.Edit.Compose.Components;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Catch.Edit
|
|
{
|
|
public partial class CatchHitObjectComposer : ScrollingHitObjectComposer<CatchHitObject>, IKeyBindingHandler<GlobalAction>
|
|
{
|
|
private const float distance_snap_radius = 50;
|
|
|
|
private CatchDistanceSnapGrid distanceSnapGrid = null!;
|
|
|
|
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
|
|
{
|
|
MinValue = 1,
|
|
MaxValue = 10,
|
|
};
|
|
|
|
[Cached(typeof(IDistanceSnapProvider))]
|
|
protected readonly CatchDistanceSnapProvider DistanceSnapProvider = new CatchDistanceSnapProvider();
|
|
|
|
public CatchHitObjectComposer(CatchRuleset ruleset)
|
|
: base(ruleset)
|
|
{
|
|
}
|
|
|
|
[BackgroundDependencyLoader]
|
|
private void load()
|
|
{
|
|
AddInternal(DistanceSnapProvider);
|
|
DistanceSnapProvider.AttachToToolbox(RightToolbox);
|
|
|
|
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
|
DistanceSnapProvider.DistanceSpacingMultiplier.Disabled = true;
|
|
|
|
LayerBelowRuleset.Add(new PlayfieldBorder
|
|
{
|
|
Anchor = Anchor.BottomCentre,
|
|
Origin = Anchor.BottomCentre,
|
|
RelativeSizeAxes = Axes.X,
|
|
Height = CatchPlayfield.HEIGHT,
|
|
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
|
});
|
|
|
|
LayerBelowRuleset.Add(distanceSnapGrid = new CatchDistanceSnapGrid(new[]
|
|
{
|
|
0.0,
|
|
Catcher.BASE_DASH_SPEED, -Catcher.BASE_DASH_SPEED,
|
|
Catcher.BASE_WALK_SPEED, -Catcher.BASE_WALK_SPEED,
|
|
}));
|
|
}
|
|
|
|
protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider);
|
|
|
|
protected override IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
|
|
=> base.CreateTernaryButtons()
|
|
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
|
|
|
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) =>
|
|
new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
|
|
{
|
|
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
|
|
};
|
|
|
|
protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this);
|
|
|
|
protected override BeatSnapGrid CreateBeatSnapGrid() => new CatchBeatSnapGrid();
|
|
|
|
protected override IReadOnlyList<CompositionTool> CompositionTools => new CompositionTool[]
|
|
{
|
|
new FruitCompositionTool(),
|
|
new JuiceStreamCompositionTool(),
|
|
new BananaShowerCompositionTool()
|
|
};
|
|
|
|
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
|
{
|
|
switch (e.Action)
|
|
{
|
|
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
|
|
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
|
|
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
|
|
case GlobalAction.IncreaseScrollSpeed:
|
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
|
|
return true;
|
|
|
|
case GlobalAction.DecreaseScrollSpeed:
|
|
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
|
{
|
|
}
|
|
|
|
protected override bool OnKeyDown(KeyDownEvent e)
|
|
{
|
|
if (e.Repeat)
|
|
return false;
|
|
|
|
handleToggleViaKey(e);
|
|
return base.OnKeyDown(e);
|
|
}
|
|
|
|
protected override void OnKeyUp(KeyUpEvent e)
|
|
{
|
|
handleToggleViaKey(e);
|
|
base.OnKeyUp(e);
|
|
}
|
|
|
|
private void handleToggleViaKey(KeyboardEvent key)
|
|
{
|
|
DistanceSnapProvider.HandleToggleViaKey(key);
|
|
}
|
|
|
|
public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All)
|
|
{
|
|
var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType);
|
|
|
|
result.ScreenSpacePosition.X = screenSpacePosition.X;
|
|
|
|
if (snapType.HasFlag(SnapType.RelativeGrids))
|
|
{
|
|
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
|
|
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
|
|
{
|
|
result = snapResult;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private PalpableCatchHitObject? getLastSnappableHitObject(double time)
|
|
{
|
|
var hitObject = EditorBeatmap.HitObjects.OfType<CatchHitObject>().LastOrDefault(h => h.GetEndTime() < time && !(h is BananaShower));
|
|
|
|
switch (hitObject)
|
|
{
|
|
case Fruit fruit:
|
|
return fruit;
|
|
|
|
case JuiceStream juiceStream:
|
|
return juiceStream.NestedHitObjects.OfType<PalpableCatchHitObject>().LastOrDefault(h => !(h is TinyDroplet));
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private PalpableCatchHitObject? getDistanceSnapGridSourceHitObject()
|
|
{
|
|
switch (BlueprintContainer.CurrentTool)
|
|
{
|
|
case SelectTool:
|
|
if (EditorBeatmap.SelectedHitObjects.Count == 0)
|
|
return null;
|
|
|
|
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
|
|
return getLastSnappableHitObject(minTime);
|
|
|
|
case FruitCompositionTool:
|
|
case JuiceStreamCompositionTool:
|
|
if (!CursorInPlacementArea)
|
|
return null;
|
|
|
|
if (EditorBeatmap.PlacementObject.Value is JuiceStream)
|
|
{
|
|
// Juice stream path is not subject to snapping.
|
|
if (BlueprintContainer.CurrentPlacement.PlacementActive is PlacementBlueprint.PlacementState.Active)
|
|
return null;
|
|
}
|
|
|
|
double timeAtCursor = ((CatchPlayfield)Playfield).TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
|
return getLastSnappableHitObject(timeAtCursor);
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
updateDistanceSnapGrid();
|
|
}
|
|
|
|
private void updateDistanceSnapGrid()
|
|
{
|
|
if (DistanceSnapProvider.DistanceSnapToggle.Value != TernaryState.True)
|
|
{
|
|
distanceSnapGrid.Hide();
|
|
return;
|
|
}
|
|
|
|
var sourceHitObject = getDistanceSnapGridSourceHitObject();
|
|
|
|
if (sourceHitObject == null)
|
|
{
|
|
distanceSnapGrid.Hide();
|
|
return;
|
|
}
|
|
|
|
distanceSnapGrid.Show();
|
|
distanceSnapGrid.StartTime = sourceHitObject.GetEndTime();
|
|
distanceSnapGrid.StartX = sourceHitObject.EffectiveX;
|
|
}
|
|
}
|
|
}
|