mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 16:27:26 +08:00
Merge branch 'master' into game-handles-links
This commit is contained in:
commit
23ad516348
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@ -85,6 +86,93 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
checkPositions();
|
checkPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointSelection()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, true);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointDeselectionViaOtherControlPoint()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointDeselectionViaClickOutside()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointSelection()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
checkControlPointSelected(0, true);
|
||||||
|
checkControlPointSelected(1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointDeselectionViaOtherControlPoint()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToControlPoint(2);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointDeselectionViaClickOutside()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
private void moveHitObject()
|
private void moveHitObject()
|
||||||
{
|
{
|
||||||
AddStep("move hitobject", () =>
|
AddStep("move hitobject", () =>
|
||||||
@ -104,11 +192,24 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void moveMouseToControlPoint(int index)
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index];
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkControlPointSelected(int index, bool selected)
|
||||||
|
=> AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected);
|
||||||
|
|
||||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
{
|
{
|
||||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||||
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||||
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||||
|
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||||
|
|
||||||
public TestSliderBlueprint(DrawableSlider slider)
|
public TestSliderBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Lines;
|
using osu.Framework.Graphics.Lines;
|
||||||
@ -11,18 +12,22 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointPiece : BlueprintPiece<Slider>
|
public class PathControlPointPiece : BlueprintPiece<Slider>
|
||||||
{
|
{
|
||||||
|
public Action<int> RequestSelection;
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
public Action<Vector2[]> ControlPointsChanged;
|
||||||
|
|
||||||
private readonly Slider slider;
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
private readonly int index;
|
public readonly int Index;
|
||||||
|
|
||||||
|
private readonly Slider slider;
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
private readonly CircularContainer marker;
|
private readonly Container marker;
|
||||||
|
private readonly Drawable markerRing;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
@ -30,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public PathControlPointPiece(Slider slider, int index)
|
public PathControlPointPiece(Slider slider, int index)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
this.index = index;
|
Index = index;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -42,13 +47,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
PathRadius = 1
|
PathRadius = 1
|
||||||
},
|
},
|
||||||
marker = new CircularContainer
|
marker = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(10),
|
AutoSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Children = new[]
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(10),
|
||||||
|
},
|
||||||
|
markerRing = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(14),
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
Alpha = 0,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -57,30 +85,66 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Position = slider.StackedPosition + slider.Path.ControlPoints[index];
|
Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
|
||||||
|
|
||||||
marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
updateMarkerDisplay();
|
||||||
|
updateConnectingPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the state of the circular control point marker.
|
||||||
|
/// </summary>
|
||||||
|
private void updateMarkerDisplay()
|
||||||
|
{
|
||||||
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
|
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
||||||
|
if (IsHovered || IsSelected.Value)
|
||||||
|
colour = Color4.White;
|
||||||
|
marker.Colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the path connecting this control point to the previous one.
|
||||||
|
/// </summary>
|
||||||
|
private void updateConnectingPath()
|
||||||
|
{
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
if (index != slider.Path.ControlPoints.Length - 1)
|
if (Index != slider.Path.ControlPoints.Length - 1)
|
||||||
{
|
{
|
||||||
path.AddVertex(Vector2.Zero);
|
path.AddVertex(Vector2.Zero);
|
||||||
path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]);
|
path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The connecting path is excluded from positional input
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (RequestSelection != null)
|
||||||
|
{
|
||||||
|
RequestSelection.Invoke(Index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||||
|
|
||||||
protected override bool OnDrag(DragEvent e)
|
protected override bool OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
||||||
|
|
||||||
if (index == 0)
|
if (Index == 0)
|
||||||
{
|
{
|
||||||
// Special handling for the head - only the position of the slider changes
|
// Special handling for the head - only the position of the slider changes
|
||||||
slider.Position += e.Delta;
|
slider.Position += e.Delta;
|
||||||
@ -90,13 +154,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
newControlPoints[i] -= e.Delta;
|
newControlPoints[i] -= e.Delta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
newControlPoints[index] += e.Delta;
|
newControlPoints[Index] += e.Delta;
|
||||||
|
|
||||||
if (isSegmentSeparatorWithNext)
|
if (isSegmentSeparatorWithNext)
|
||||||
newControlPoints[index + 1] = newControlPoints[index];
|
newControlPoints[Index + 1] = newControlPoints[Index];
|
||||||
|
|
||||||
if (isSegmentSeparatorWithPrevious)
|
if (isSegmentSeparatorWithPrevious)
|
||||||
newControlPoints[index - 1] = newControlPoints[index];
|
newControlPoints[Index - 1] = newControlPoints[Index];
|
||||||
|
|
||||||
ControlPointsChanged?.Invoke(newControlPoints);
|
ControlPointsChanged?.Invoke(newControlPoints);
|
||||||
|
|
||||||
@ -107,8 +171,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index];
|
private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index];
|
private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -13,25 +15,66 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
public Action<Vector2[]> ControlPointsChanged;
|
||||||
|
|
||||||
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
private readonly Container<PathControlPointPiece> pieces;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider)
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
|
this.allowSelection = allowSelection;
|
||||||
|
|
||||||
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length > pieces.Count)
|
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
||||||
pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) });
|
{
|
||||||
while (slider.Path.ControlPoints.Length < pieces.Count)
|
var piece = new PathControlPointPiece(slider, Pieces.Count)
|
||||||
pieces.Remove(pieces[pieces.Count - 1]);
|
{
|
||||||
|
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (allowSelection)
|
||||||
|
piece.RequestSelection = selectPiece;
|
||||||
|
|
||||||
|
Pieces.Add(piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
||||||
|
Pieces.Remove(Pieces[Pieces.Count - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectPiece(int index)
|
||||||
|
{
|
||||||
|
if (inputManager.CurrentState.Keyboard.ControlPressed)
|
||||||
|
Pieces[index].IsSelected.Toggle();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = piece.Index == index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
bodyPiece = new SliderBodyPiece(),
|
bodyPiece = new SliderBodyPiece(),
|
||||||
headCirclePiece = new HitCirclePiece(),
|
headCirclePiece = new HitCirclePiece(),
|
||||||
tailCirclePiece = new HitCirclePiece(),
|
tailCirclePiece = new HitCirclePiece(),
|
||||||
new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
|
new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() },
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
|
@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected readonly SliderBodyPiece BodyPiece;
|
protected readonly SliderBodyPiece BodyPiece;
|
||||||
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
|
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
|
||||||
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
|
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
|
||||||
|
protected readonly PathControlPointVisualiser ControlPointVisualiser;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||||
new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
|
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
||||||
|
|
||||||
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
|
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
|
||||||
|
|
||||||
|
UpdateHitObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
|
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
|
||||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
PathBindable.Value = value;
|
PathBindable.Value = value;
|
||||||
endPositionCache.Invalidate();
|
endPositionCache.Invalidate();
|
||||||
|
|
||||||
|
updateNestedPositions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,14 +50,9 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.Position = value;
|
base.Position = value;
|
||||||
|
|
||||||
endPositionCache.Invalidate();
|
endPositionCache.Invalidate();
|
||||||
|
|
||||||
if (HeadCircle != null)
|
updateNestedPositions();
|
||||||
HeadCircle.Position = value;
|
|
||||||
|
|
||||||
if (TailCircle != null)
|
|
||||||
TailCircle.Position = EndPosition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateNestedPositions()
|
||||||
|
{
|
||||||
|
if (HeadCircle != null)
|
||||||
|
HeadCircle.Position = Position;
|
||||||
|
|
||||||
|
if (TailCircle != null)
|
||||||
|
TailCircle.Position = EndPosition;
|
||||||
|
}
|
||||||
|
|
||||||
private List<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
private List<HitSampleInfo> getNodeSamples(int nodeIndex) =>
|
||||||
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
|
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
|
||||||
|
|
||||||
|
@ -7,11 +7,10 @@ using osu.Game.Online;
|
|||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Game.Screens.Ranking.Pages;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
@ -42,7 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(80, 40),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Ranking.Pages;
|
using osu.Game.Screens.Ranking.Pages;
|
||||||
@ -22,11 +27,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(ScoreInfo),
|
|
||||||
typeof(Results),
|
typeof(Results),
|
||||||
typeof(ResultsPage),
|
typeof(ResultsPage),
|
||||||
typeof(ScoreResultsPage),
|
typeof(ScoreResultsPage),
|
||||||
typeof(LocalLeaderboardPage)
|
typeof(RetryButton),
|
||||||
|
typeof(ReplayDownloadButton),
|
||||||
|
typeof(LocalLeaderboardPage),
|
||||||
|
typeof(TestPlayer)
|
||||||
};
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -42,26 +49,82 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
|
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
|
||||||
if (beatmapInfo != null)
|
if (beatmapInfo != null)
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
|
}
|
||||||
|
|
||||||
LoadScreen(new SoloResults(new ScoreInfo
|
private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo
|
||||||
|
{
|
||||||
|
TotalScore = 2845370,
|
||||||
|
Accuracy = 0.98,
|
||||||
|
MaxCombo = 123,
|
||||||
|
Rank = ScoreRank.A,
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
Statistics = new Dictionary<HitResult, int>
|
||||||
{
|
{
|
||||||
TotalScore = 2845370,
|
{ HitResult.Great, 50 },
|
||||||
Accuracy = 0.98,
|
{ HitResult.Good, 20 },
|
||||||
MaxCombo = 123,
|
{ HitResult.Meh, 50 },
|
||||||
Rank = ScoreRank.A,
|
{ HitResult.Miss, 1 }
|
||||||
Date = DateTimeOffset.Now,
|
},
|
||||||
Statistics = new Dictionary<HitResult, int>
|
User = new User
|
||||||
|
{
|
||||||
|
Username = "peppy",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResultsWithoutPlayer()
|
||||||
|
{
|
||||||
|
TestSoloResults screen = null;
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResultsWithPlayer()
|
||||||
|
{
|
||||||
|
TestSoloResults screen = null;
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestResultsContainer : Container
|
||||||
|
{
|
||||||
|
[Cached(typeof(Player))]
|
||||||
|
private readonly Player player = new TestPlayer();
|
||||||
|
|
||||||
|
public TestResultsContainer(IScreen screen)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new OsuScreenStack(screen)
|
||||||
{
|
{
|
||||||
{ HitResult.Great, 50 },
|
RelativeSizeAxes = Axes.Both,
|
||||||
{ HitResult.Good, 20 },
|
};
|
||||||
{ HitResult.Meh, 50 },
|
}
|
||||||
{ HitResult.Miss, 1 }
|
}
|
||||||
},
|
|
||||||
User = new User
|
private class TestSoloResults : SoloResults
|
||||||
{
|
{
|
||||||
Username = "peppy",
|
public HotkeyRetryOverlay RetryOverlay;
|
||||||
}
|
|
||||||
}));
|
public TestSoloResults(ScoreInfo score)
|
||||||
|
: base(score)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Users.Drawables;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
@ -37,6 +38,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
private readonly int rank;
|
private readonly int rank;
|
||||||
|
private readonly bool allowHighlight;
|
||||||
|
|
||||||
private Box background;
|
private Box background;
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -49,17 +51,18 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
private List<ScoreComponentLabel> statisticsLabels;
|
private List<ScoreComponentLabel> statisticsLabels;
|
||||||
|
|
||||||
public LeaderboardScore(ScoreInfo score, int rank)
|
public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.rank = rank;
|
this.rank = rank;
|
||||||
|
this.allowHighlight = allowHighlight;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = HEIGHT;
|
Height = HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(IAPIProvider api, OsuColour colour)
|
||||||
{
|
{
|
||||||
var user = score.User;
|
var user = score.User;
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
background = new Box
|
background = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black,
|
Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black,
|
||||||
Alpha = background_alpha,
|
Alpha = background_alpha,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
|
EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
|
||||||
EditorBeatmap.HitObjectAdded += addHitObject;
|
EditorBeatmap.HitObjectAdded += addHitObject;
|
||||||
EditorBeatmap.HitObjectRemoved += removeHitObject;
|
EditorBeatmap.HitObjectRemoved += removeHitObject;
|
||||||
EditorBeatmap.StartTimeChanged += updateHitObject;
|
EditorBeatmap.StartTimeChanged += UpdateHitObject;
|
||||||
|
|
||||||
var dependencies = new DependencyContainer(parent);
|
var dependencies = new DependencyContainer(parent);
|
||||||
dependencies.CacheAs<IEditorBeatmap>(EditorBeatmap);
|
dependencies.CacheAs<IEditorBeatmap>(EditorBeatmap);
|
||||||
@ -225,11 +225,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
private ScheduledDelegate scheduledUpdate;
|
private ScheduledDelegate scheduledUpdate;
|
||||||
|
|
||||||
private void addHitObject(HitObject hitObject) => updateHitObject(hitObject);
|
public override void UpdateHitObject(HitObject hitObject)
|
||||||
|
|
||||||
private void removeHitObject(HitObject hitObject) => updateHitObject(null);
|
|
||||||
|
|
||||||
private void updateHitObject([CanBeNull] HitObject hitObject)
|
|
||||||
{
|
{
|
||||||
scheduledUpdate?.Cancel();
|
scheduledUpdate?.Cancel();
|
||||||
scheduledUpdate = Schedule(() =>
|
scheduledUpdate = Schedule(() =>
|
||||||
@ -240,6 +236,10 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject);
|
||||||
|
|
||||||
|
private void removeHitObject(HitObject hitObject) => UpdateHitObject(null);
|
||||||
|
|
||||||
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
|
public override IEnumerable<DrawableHitObject> HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects;
|
||||||
public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
|
public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position);
|
||||||
|
|
||||||
@ -351,11 +351,22 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a <see cref="HitObject"/>, invoking <see cref="HitObject.ApplyDefaults"/> and re-processing the beatmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> to update.</param>
|
||||||
|
public abstract void UpdateHitObject([CanBeNull] HitObject hitObject);
|
||||||
|
|
||||||
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
|
||||||
|
|
||||||
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
public abstract float GetBeatSnapDistanceAt(double referenceTime);
|
||||||
|
|
||||||
public abstract float DurationToDistance(double referenceTime, double duration);
|
public abstract float DurationToDistance(double referenceTime, double duration);
|
||||||
|
|
||||||
public abstract double DistanceToDuration(double referenceTime, float distance);
|
public abstract double DistanceToDuration(double referenceTime, float distance);
|
||||||
|
|
||||||
public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance);
|
public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance);
|
||||||
|
|
||||||
public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance);
|
public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -36,6 +38,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
public override bool HandlePositionalInput => ShouldBeAlive;
|
public override bool HandlePositionalInput => ShouldBeAlive;
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private HitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
protected SelectionBlueprint(DrawableHitObject drawableObject)
|
protected SelectionBlueprint(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
DrawableObject = drawableObject;
|
DrawableObject = drawableObject;
|
||||||
@ -89,6 +94,11 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
public bool IsSelected => State == SelectionState.Selected;
|
public bool IsSelected => State == SelectionState.Selected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the <see cref="HitObject"/>, invoking <see cref="HitObject.ApplyDefaults"/> and re-processing the beatmap.
|
||||||
|
/// </summary>
|
||||||
|
protected void UpdateHitObject() => composer?.UpdateHitObject(DrawableObject.HitObject);
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Screens.Play
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
|
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly IAdjustableClock sourceClock;
|
private IAdjustableClock sourceClock;
|
||||||
|
|
||||||
public readonly BindableBool IsPaused = new BindableBool();
|
public readonly BindableBool IsPaused = new BindableBool();
|
||||||
|
|
||||||
@ -153,6 +154,18 @@ namespace osu.Game.Screens.Play
|
|||||||
IsPaused.Value = true;
|
IsPaused.Value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the backing clock to avoid using the originally provided beatmap's track.
|
||||||
|
/// </summary>
|
||||||
|
public void StopUsingBeatmapClock()
|
||||||
|
{
|
||||||
|
if (sourceClock != beatmap.Track)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sourceClock = new TrackVirtual(beatmap.Track.Length);
|
||||||
|
adjustableClock.ChangeSource(sourceClock);
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetLocalAdjustments()
|
public void ResetLocalAdjustments()
|
||||||
{
|
{
|
||||||
// In the case of replays, we may have changed the playback rate.
|
// In the case of replays, we may have changed the playback rate.
|
||||||
|
@ -30,6 +30,7 @@ using osu.Game.Users;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class Player : ScreenWithBeatmapBackground
|
public class Player : ScreenWithBeatmapBackground
|
||||||
{
|
{
|
||||||
public override bool AllowBackButton => false; // handled by HoldForMenuButton
|
public override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||||
@ -311,14 +312,19 @@ namespace osu.Game.Screens.Play
|
|||||||
this.Exit();
|
this.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
|
||||||
|
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
|
||||||
|
/// </summary>
|
||||||
public void Restart()
|
public void Restart()
|
||||||
{
|
{
|
||||||
if (!this.IsCurrentScreen()) return;
|
|
||||||
|
|
||||||
sampleRestart?.Play();
|
sampleRestart?.Play();
|
||||||
|
|
||||||
RestartRequested?.Invoke();
|
RestartRequested?.Invoke();
|
||||||
performImmediateExit();
|
|
||||||
|
if (this.IsCurrentScreen())
|
||||||
|
performImmediateExit();
|
||||||
|
else
|
||||||
|
this.MakeCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate completionProgressDelegate;
|
private ScheduledDelegate completionProgressDelegate;
|
||||||
@ -532,6 +538,10 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
GameplayClockContainer.ResetLocalAdjustments();
|
GameplayClockContainer.ResetLocalAdjustments();
|
||||||
|
|
||||||
|
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
||||||
|
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
||||||
|
GameplayClockContainer.StopUsingBeatmapClock();
|
||||||
|
|
||||||
fadeOut();
|
fadeOut();
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,13 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online;
|
|
||||||
using osu.Game.Scoring;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Ranking.Pages
|
||||||
{
|
{
|
||||||
public class ReplayDownloadButton : DownloadTrackingComposite<ScoreInfo, ScoreManager>
|
public class ReplayDownloadButton : DownloadTrackingComposite<ScoreInfo, ScoreManager>
|
||||||
{
|
{
|
||||||
@ -33,6 +34,7 @@ namespace osu.Game.Screens.Play
|
|||||||
public ReplayDownloadButton(ScoreInfo score)
|
public ReplayDownloadButton(ScoreInfo score)
|
||||||
: base(score)
|
: base(score)
|
||||||
{
|
{
|
||||||
|
Size = new Vector2(50, 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
54
osu.Game/Screens/Ranking/Pages/RetryButton.cs
Normal file
54
osu.Game/Screens/Ranking/Pages/RetryButton.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Ranking.Pages
|
||||||
|
{
|
||||||
|
public class RetryButton : OsuAnimatedButton
|
||||||
|
{
|
||||||
|
private readonly Box background;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Player player { get; set; }
|
||||||
|
|
||||||
|
public RetryButton()
|
||||||
|
{
|
||||||
|
Size = new Vector2(50, 30);
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = float.MaxValue
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(13),
|
||||||
|
Icon = FontAwesome.Solid.ArrowCircleLeft,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
TooltipText = "Retry";
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
background.Colour = colours.Green;
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
Action = () => player.Restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -169,12 +169,19 @@ namespace osu.Game.Screens.Ranking.Pages
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new ReplayDownloadButton(score)
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Margin = new MarginPadding { Bottom = 10 },
|
Margin = new MarginPadding { Bottom = 10 },
|
||||||
Size = new Vector2(50, 30),
|
Spacing = new Vector2(5),
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ReplayDownloadButton(score),
|
||||||
|
new RetryButton()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Ranking
|
namespace osu.Game.Screens.Ranking
|
||||||
{
|
{
|
||||||
@ -34,6 +35,9 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
private ResultModeTabControl modeChangeButtons;
|
private ResultModeTabControl modeChangeButtons;
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private Player player { get; set; }
|
||||||
|
|
||||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||||
|
|
||||||
protected readonly ScoreInfo Score;
|
protected readonly ScoreInfo Score;
|
||||||
@ -100,10 +104,7 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
allCircles.ForEach(c =>
|
allCircles.ForEach(c => c.ScaleTo(0, transition_time, Easing.OutSine));
|
||||||
{
|
|
||||||
c.ScaleTo(0, transition_time, Easing.OutSine);
|
|
||||||
});
|
|
||||||
|
|
||||||
Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint);
|
Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint);
|
||||||
|
|
||||||
@ -115,147 +116,157 @@ namespace osu.Game.Screens.Ranking
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChild = new AspectContainer
|
||||||
{
|
{
|
||||||
new AspectContainer
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = overscan,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
circleOuterBackground = new CircularContainer
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Height = overscan,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
circleOuterBackground = new CircularContainer
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Box
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
Alpha = 0.2f,
|
||||||
{
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.2f,
|
Colour = Color4.Black,
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
circleOuter = new CircularContainer
|
},
|
||||||
|
circleOuter = new CircularContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(circle_outer_scale),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Size = new Vector2(circle_outer_scale),
|
Colour = Color4.Black.Opacity(0.4f),
|
||||||
EdgeEffect = new EdgeEffectParameters
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 15,
|
||||||
|
},
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
{
|
{
|
||||||
Colour = Color4.Black.Opacity(0.4f),
|
RelativeSizeAxes = Axes.Both,
|
||||||
Type = EdgeEffectType.Shadow,
|
Colour = Color4.White,
|
||||||
Radius = 15,
|
|
||||||
},
|
},
|
||||||
RelativeSizeAxes = Axes.Both,
|
backgroundParallax = new ParallaxContainer
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ParallaxAmount = 0.01f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Sprite
|
||||||
Colour = Color4.White,
|
|
||||||
},
|
|
||||||
backgroundParallax = new ParallaxContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ParallaxAmount = 0.01f,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Sprite
|
RelativeSizeAxes = Axes.Both,
|
||||||
{
|
Alpha = 0.2f,
|
||||||
RelativeSizeAxes = Axes.Both,
|
Texture = Beatmap.Value.Background,
|
||||||
Alpha = 0.2f,
|
Anchor = Anchor.Centre,
|
||||||
Texture = Beatmap.Value.Background,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
FillMode = FillMode.Fill
|
||||||
Origin = Anchor.Centre,
|
|
||||||
FillMode = FillMode.Fill
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
modeChangeButtons = new ResultModeTabControl
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 50,
|
|
||||||
Margin = new MarginPadding { Bottom = 110 },
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Text = $"{Score.MaxCombo}x",
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
|
||||||
X = 0.1f,
|
|
||||||
Colour = colours.BlueDarker,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = "max combo",
|
|
||||||
Font = OsuFont.GetFont(size: 20),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.1f,
|
|
||||||
Colour = colours.Gray6,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Text = $"{Score.Accuracy:P2}",
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.9f,
|
|
||||||
Colour = colours.BlueDarker,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = "accuracy",
|
|
||||||
Font = OsuFont.GetFont(size: 20),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.9f,
|
|
||||||
Colour = colours.Gray6,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
circleInner = new CircularContainer
|
|
||||||
{
|
|
||||||
Size = new Vector2(0.6f),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Colour = Color4.Black.Opacity(0.4f),
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Radius = 15,
|
|
||||||
},
|
},
|
||||||
RelativeSizeAxes = Axes.Both,
|
modeChangeButtons = new ResultModeTabControl
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
Anchor = Anchor.BottomCentre,
|
||||||
{
|
Origin = Anchor.BottomCentre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Colour = Color4.White,
|
Height = 50,
|
||||||
},
|
Margin = new MarginPadding { Bottom = 110 },
|
||||||
}
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Text = $"{Score.MaxCombo}x",
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
||||||
|
X = 0.1f,
|
||||||
|
Colour = colours.BlueDarker,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "max combo",
|
||||||
|
Font = OsuFont.GetFont(size: 20),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.1f,
|
||||||
|
Colour = colours.Gray6,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Text = $"{Score.Accuracy:P2}",
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.9f,
|
||||||
|
Colour = colours.BlueDarker,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "accuracy",
|
||||||
|
Font = OsuFont.GetFont(size: 20),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.9f,
|
||||||
|
Colour = colours.Gray6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
circleInner = new CircularContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(0.6f),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = Color4.Black.Opacity(0.4f),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 15,
|
||||||
|
},
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
AddInternal(new HotkeyRetryOverlay
|
||||||
|
{
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (!this.IsCurrentScreen()) return;
|
||||||
|
|
||||||
|
player?.Restart();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var pages = CreateResultPages();
|
var pages = CreateResultPages();
|
||||||
|
|
||||||
foreach (var p in pages)
|
foreach (var p in pages)
|
||||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index)
|
protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope)
|
||||||
{
|
{
|
||||||
Action = () => ScoreSelected?.Invoke(model)
|
Action = () => ScoreSelected?.Invoke(model)
|
||||||
};
|
};
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
if (newScore == null)
|
if (newScore == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position)
|
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false)
|
||||||
{
|
{
|
||||||
Action = () => ScoreSelected?.Invoke(newScore.Score)
|
Action = () => ScoreSelected?.Invoke(newScore.Score)
|
||||||
}, drawableScore =>
|
}, drawableScore =>
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
public abstract class SelectionBlueprintTestScene : OsuTestScene
|
public abstract class SelectionBlueprintTestScene : ManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
protected override Container<Drawable> Content => content ?? base.Content;
|
protected override Container<Drawable> Content => content ?? base.Content;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
Loading…
Reference in New Issue
Block a user