mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 08:12:56 +08:00
Merge branch 'master' into multiplayer-spectator-screen
This commit is contained in:
commit
125358158b
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.402.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.407.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -0,0 +1,175 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneSliderControlPointPiece : SelectionBlueprintTestScene
|
||||||
|
{
|
||||||
|
private Slider slider;
|
||||||
|
private DrawableSlider drawableObject;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
slider = new Slider
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(150, 150)),
|
||||||
|
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(400, 0)),
|
||||||
|
new PathControlPoint(new Vector2(400, 150))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
|
Add(drawableObject = new DrawableSlider(slider));
|
||||||
|
AddBlueprint(new TestSliderBlueprint(drawableObject));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragControlPoint()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(150, 50));
|
||||||
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointPosition(1, new Vector2(150, 50));
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragControlPointAlmostLinearlyExterior()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 0.01f));
|
||||||
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointPosition(1, new Vector2(400, 0.01f));
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragControlPointPathRecovery()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 0.01f));
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(150, 50));
|
||||||
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointPosition(1, new Vector2(150, 50));
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragControlPointPathRecoveryOtherSegment()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(4);
|
||||||
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(350, 0.01f));
|
||||||
|
assertControlPointType(2, PathType.Bezier);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(150, 150));
|
||||||
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointPosition(4, new Vector2(150, 150));
|
||||||
|
assertControlPointType(2, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragControlPointPathAfterChangingType()
|
||||||
|
{
|
||||||
|
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type.Value = PathType.Bezier);
|
||||||
|
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
|
||||||
|
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type.Value = PathType.PerfectCurve);
|
||||||
|
|
||||||
|
moveMouseToControlPoint(4);
|
||||||
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointType(3, PathType.PerfectCurve);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(350, 0.01f));
|
||||||
|
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
assertControlPointPosition(4, new Vector2(350, 0.01f));
|
||||||
|
assertControlPointType(3, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMovementStep(Vector2 relativePosition)
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to {relativePosition}", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + relativePosition;
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouseToControlPoint(int index)
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type.Value == type);
|
||||||
|
|
||||||
|
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||||
|
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position.Value, 1));
|
||||||
|
|
||||||
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
|
{
|
||||||
|
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||||
|
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||||
|
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||||
|
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||||
|
|
||||||
|
public TestSliderBlueprint(DrawableSlider slider)
|
||||||
|
: base(slider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
||||||
|
{
|
||||||
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
|
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
||||||
|
: base(slider, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -276,6 +276,104 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
assertControlPointType(0, PathType.Linear);
|
assertControlPointType(0, PathType.Linear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentAlmostLinearlyExterior()
|
||||||
|
{
|
||||||
|
Vector2 startPosition = new Vector2(200);
|
||||||
|
|
||||||
|
addMovementStep(startPosition);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(300, 0));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(150, 0.1f));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentRecovery()
|
||||||
|
{
|
||||||
|
Vector2 startPosition = new Vector2(200);
|
||||||
|
|
||||||
|
addMovementStep(startPosition);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(300, 0));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(150, 0.1f)); // Should convert to bezier
|
||||||
|
addMovementStep(startPosition + new Vector2(400.0f, 50.0f)); // Should convert back to perfect
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentLarge()
|
||||||
|
{
|
||||||
|
Vector2 startPosition = new Vector2(400);
|
||||||
|
|
||||||
|
addMovementStep(startPosition);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(220, 220));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
// Playfield dimensions are 640 x 480.
|
||||||
|
// So a 440 x 440 bounding box should be ok.
|
||||||
|
addMovementStep(startPosition + new Vector2(-220, 220));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentTooLarge()
|
||||||
|
{
|
||||||
|
Vector2 startPosition = new Vector2(480, 200);
|
||||||
|
|
||||||
|
addMovementStep(startPosition);
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(startPosition + new Vector2(400, 400));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
// Playfield dimensions are 640 x 480.
|
||||||
|
// So an 800 * 800 bounding box area should not be ok.
|
||||||
|
addMovementStep(startPosition + new Vector2(-400, 400));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacePerfectCurveSegmentCompleteArc()
|
||||||
|
{
|
||||||
|
addMovementStep(new Vector2(400));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(600, 400));
|
||||||
|
addClickStep(MouseButton.Left);
|
||||||
|
|
||||||
|
addMovementStep(new Vector2(400, 410));
|
||||||
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
|
assertPlaced(true);
|
||||||
|
assertControlPointCount(3);
|
||||||
|
assertControlPointType(0, PathType.PerfectCurve);
|
||||||
|
}
|
||||||
|
|
||||||
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));
|
||||||
|
|
||||||
private void addClickStep(MouseButton button)
|
private void addClickStep(MouseButton button)
|
||||||
|
@ -2,14 +2,18 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -28,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
|
public class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
|
||||||
{
|
{
|
||||||
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
||||||
|
public List<PathControlPoint> PointsInSegment;
|
||||||
|
|
||||||
public readonly BindableBool IsSelected = new BindableBool();
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
public readonly PathControlPoint ControlPoint;
|
public readonly PathControlPoint ControlPoint;
|
||||||
@ -54,6 +59,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
ControlPoint = controlPoint;
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
|
slider.Path.Version.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
||||||
|
updatePathType();
|
||||||
|
}, runOnceImmediately: true);
|
||||||
|
|
||||||
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
@ -150,6 +161,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
private Vector2 dragStartPosition;
|
private Vector2 dragStartPosition;
|
||||||
|
private PathType? dragPathType;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
{
|
{
|
||||||
@ -159,6 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (e.Button == MouseButton.Left)
|
if (e.Button == MouseButton.Left)
|
||||||
{
|
{
|
||||||
dragStartPosition = ControlPoint.Position.Value;
|
dragStartPosition = ControlPoint.Position.Value;
|
||||||
|
dragPathType = PointsInSegment[0].Type.Value;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -184,10 +198,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||||
|
|
||||||
|
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||||
|
PointsInSegment[0].Type.Value = dragPathType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles correction of invalid path types.
|
||||||
|
/// </summary>
|
||||||
|
private void updatePathType()
|
||||||
|
{
|
||||||
|
if (ControlPoint.Type.Value != PathType.PerfectCurve)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position.Value).ToArray();
|
||||||
|
if (points.Length != 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||||
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
|
ControlPoint.Type.Value = PathType.Bezier;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the state of the circular control point marker.
|
/// Updates the state of the circular control point marker.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -142,6 +142,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
updateSlider();
|
updateSlider();
|
||||||
|
|
||||||
|
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||||
|
updatePathType();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePathType()
|
private void updatePathType()
|
||||||
|
@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
base.OnOperationEnded();
|
base.OnOperationEnded();
|
||||||
referenceOrigin = null;
|
referenceOrigin = null;
|
||||||
|
referencePathTypes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||||
@ -53,6 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Vector2? referenceOrigin;
|
private Vector2? referenceOrigin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// During a transform, the initial path types of a single selected slider are stored so they
|
||||||
|
/// can be maintained throughout the operation.
|
||||||
|
/// </summary>
|
||||||
|
private List<PathType?> referencePathTypes;
|
||||||
|
|
||||||
public override bool HandleReverse()
|
public override bool HandleReverse()
|
||||||
{
|
{
|
||||||
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
||||||
@ -194,6 +201,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private void scaleSlider(Slider slider, Vector2 scale)
|
private void scaleSlider(Slider slider, Vector2 scale)
|
||||||
{
|
{
|
||||||
|
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
||||||
|
|
||||||
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
||||||
|
|
||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||||
@ -209,6 +218,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
point.Position.Value *= pathRelativeDeltaScale;
|
point.Position.Value *= pathRelativeDeltaScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
||||||
|
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
||||||
|
slider.Path.ControlPoints[i].Type.Value = referencePathTypes[i];
|
||||||
|
|
||||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||||
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGameBase game)
|
private void load(OsuGameBase game)
|
||||||
{
|
{
|
||||||
Child = globalActionContainer = new GlobalActionContainer(game, null);
|
Child = globalActionContainer = new GlobalActionContainer(game);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
|
@ -209,9 +209,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
addClickButtonStep();
|
addClickButtonStep();
|
||||||
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
|
||||||
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
|
||||||
|
|
||||||
|
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
|
||||||
|
|
||||||
|
AddStep("finish gameplay", () =>
|
||||||
|
{
|
||||||
|
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.Loaded);
|
||||||
|
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay);
|
||||||
|
});
|
||||||
|
|
||||||
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,20 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
exitViaEscapeAndConfirm();
|
exitViaEscapeAndConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding
|
||||||
|
/// but should be handled *after* song select).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestOpenModSelectOverlayUsingAction()
|
||||||
|
{
|
||||||
|
TestSongSelect songSelect = null;
|
||||||
|
|
||||||
|
PushAndConfirm(() => songSelect = new TestSongSelect());
|
||||||
|
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||||
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestRetryCountIncrements()
|
public void TestRetryCountIncrements()
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
@ -13,20 +12,23 @@ namespace osu.Game.Input.Bindings
|
|||||||
{
|
{
|
||||||
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalKeyboardInput
|
||||||
{
|
{
|
||||||
[CanBeNull]
|
|
||||||
private readonly GlobalInputManager globalInputManager;
|
|
||||||
|
|
||||||
private readonly Drawable handler;
|
private readonly Drawable handler;
|
||||||
|
private InputManager parentInputManager;
|
||||||
|
|
||||||
public GlobalActionContainer(OsuGameBase game, [CanBeNull] GlobalInputManager globalInputManager)
|
public GlobalActionContainer(OsuGameBase game)
|
||||||
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
: base(matchingMode: KeyCombinationMatchingMode.Modifiers)
|
||||||
{
|
{
|
||||||
this.globalInputManager = globalInputManager;
|
|
||||||
|
|
||||||
if (game is IKeyBindingHandler<GlobalAction>)
|
if (game is IKeyBindingHandler<GlobalAction>)
|
||||||
handler = game;
|
handler = game;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
parentInputManager = GetContainingInputManager();
|
||||||
|
}
|
||||||
|
|
||||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||||
.Concat(EditorKeyBindings)
|
.Concat(EditorKeyBindings)
|
||||||
.Concat(InGameKeyBindings)
|
.Concat(InGameKeyBindings)
|
||||||
@ -113,7 +115,12 @@ namespace osu.Game.Input.Bindings
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var inputQueue = globalInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue;
|
// To ensure the global actions are handled with priority, this GlobalActionContainer is actually placed after game content.
|
||||||
|
// It does not contain children as expected, so we need to forward the NonPositionalInputQueue from the parent input manager to correctly
|
||||||
|
// allow the whole game to handle these actions.
|
||||||
|
|
||||||
|
// An eventual solution to this hack is to create localised action containers for individual components like SongSelect, but this will take some rearranging.
|
||||||
|
var inputQueue = parentInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue;
|
||||||
|
|
||||||
return handler != null ? inputQueue.Prepend(handler) : inputQueue;
|
return handler != null ? inputQueue.Prepend(handler) : inputQueue;
|
||||||
}
|
}
|
||||||
|
@ -1,29 +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 osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Input.Bindings
|
|
||||||
{
|
|
||||||
public class GlobalInputManager : PassThroughInputManager
|
|
||||||
{
|
|
||||||
public readonly GlobalActionContainer GlobalBindings;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content { get; }
|
|
||||||
|
|
||||||
public GlobalInputManager(OsuGameBase game)
|
|
||||||
{
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
Content = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
// to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
|
|
||||||
GlobalBindings = new GlobalActionContainer(game, this)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -308,18 +308,21 @@ namespace osu.Game
|
|||||||
|
|
||||||
AddInternal(RulesetConfigCache);
|
AddInternal(RulesetConfigCache);
|
||||||
|
|
||||||
var globalInput = new GlobalInputManager(this)
|
GlobalActionContainer globalBindings;
|
||||||
|
|
||||||
|
var mainContent = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both },
|
||||||
Child = MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }
|
// to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
|
||||||
|
globalBindings = new GlobalActionContainer(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both };
|
MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
base.Content.Add(CreateScalingContainer().WithChild(globalInput));
|
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
|
||||||
|
|
||||||
KeyBindingStore.Register(globalInput.GlobalBindings);
|
KeyBindingStore.Register(globalBindings);
|
||||||
dependencies.Cache(globalInput.GlobalBindings);
|
dependencies.Cache(globalBindings);
|
||||||
|
|
||||||
PreviewTrackManager previewTrackManager;
|
PreviewTrackManager previewTrackManager;
|
||||||
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
||||||
|
@ -156,6 +156,39 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
return interpolateVertices(indexOfDistance(d), d);
|
return interpolateVertices(indexOfDistance(d), d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the control points belonging to the same segment as the one given.
|
||||||
|
/// The first point has a PathType which all other points inherit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPoint">One of the control points in the segment.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
|
||||||
|
|
||||||
|
foreach (PathControlPoint point in ControlPoints)
|
||||||
|
{
|
||||||
|
if (point.Type.Value != null)
|
||||||
|
{
|
||||||
|
if (!found)
|
||||||
|
pointsInCurrentSegment.Clear();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pointsInCurrentSegment.Add(point);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pointsInCurrentSegment.Add(point);
|
||||||
|
|
||||||
|
if (point == controlPoint)
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointsInCurrentSegment;
|
||||||
|
}
|
||||||
|
|
||||||
private void invalidate()
|
private void invalidate()
|
||||||
{
|
{
|
||||||
pathCache.Invalidate();
|
pathCache.Invalidate();
|
||||||
|
@ -105,14 +105,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.Enabled.Value = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||||
|
|
||||||
// When the local user is the host and spectating the match, the "start match" state should be enabled.
|
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
||||||
if (localUser.State == MultiplayerUserState.Spectating)
|
if (localUser.State == MultiplayerUserState.Spectating)
|
||||||
{
|
enableButton &= Room?.Host?.Equals(localUser) == true && newCountReady > 0;
|
||||||
button.Enabled.Value &= Room?.Host?.Equals(localUser) == true;
|
|
||||||
button.Enabled.Value &= newCountReady > 0;
|
button.Enabled.Value = enableButton;
|
||||||
}
|
|
||||||
|
|
||||||
if (newCountReady != countReady)
|
if (newCountReady != countReady)
|
||||||
{
|
{
|
||||||
|
@ -68,16 +68,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
button.Text = "Spectate";
|
button.Text = "Spectate";
|
||||||
button.BackgroundColour = colours.Blue;
|
button.BackgroundColour = colours.BlueDark;
|
||||||
button.Triangles.ColourDark = colours.Blue;
|
button.Triangles.ColourDark = colours.BlueDarker;
|
||||||
button.Triangles.ColourLight = colours.BlueLight;
|
button.Triangles.ColourLight = colours.Blue;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
button.Text = "Stop spectating";
|
button.Text = "Stop spectating";
|
||||||
button.BackgroundColour = colours.Red;
|
button.BackgroundColour = colours.Gray4;
|
||||||
button.Triangles.ColourDark = colours.Red;
|
button.Triangles.ColourDark = colours.Gray5;
|
||||||
button.Triangles.ColourLight = colours.RedLight;
|
button.Triangles.ColourLight = colours.Gray6;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
|||||||
|
|
||||||
case MultiplayerUserState.Spectating:
|
case MultiplayerUserState.Spectating:
|
||||||
text.Text = "spectating";
|
text.Text = "spectating";
|
||||||
icon.Icon = FontAwesome.Solid.Eye;
|
icon.Icon = FontAwesome.Solid.Binoculars;
|
||||||
icon.Colour = colours.BlueLight;
|
icon.Colour = colours.BlueLight;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
if (CreateNestedActionContainer)
|
if (CreateNestedActionContainer)
|
||||||
{
|
{
|
||||||
mainContent = new GlobalActionContainer(null, null).WithChild(mainContent);
|
mainContent = new GlobalActionContainer(null).WithChild(mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.Content.AddRange(new Drawable[]
|
base.Content.AddRange(new Drawable[]
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.402.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.407.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||||
<PackageReference Include="Sentry" Version="3.2.0" />
|
<PackageReference Include="Sentry" Version="3.2.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.402.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.407.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.402.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.407.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user