mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 14:17:26 +08:00
Merge branch 'master' into rankings-overlay-refactor
This commit is contained in:
commit
e288706802
@ -54,6 +54,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1205.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1210.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
11
osu.Desktop/Properties/launchSettings.json
Normal file
11
osu.Desktop/Properties/launchSettings.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"osu! Desktop": {
|
||||||
|
"commandName": "Project"
|
||||||
|
},
|
||||||
|
"osu! Tournament": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "--tournament"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,11 +24,11 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="System.IO.Packaging" Version="4.6.0" />
|
<PackageReference Include="System.IO.Packaging" Version="4.7.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
|||||||
{
|
{
|
||||||
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
|
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
|
||||||
private const uint int_mask = 0x7FFFFFFF;
|
private const uint int_mask = 0x7FFFFFFF;
|
||||||
private const uint y = 842502087;
|
private const uint y_initial = 842502087;
|
||||||
private const uint z = 3579807591;
|
private const uint z_initial = 3579807591;
|
||||||
private const uint w = 273326509;
|
private const uint w_initial = 273326509;
|
||||||
private uint _x, _y = y, _z = z, _w = w;
|
private uint x, y = y_initial, z = z_initial, w = w_initial;
|
||||||
|
|
||||||
public FastRandom(int seed)
|
public FastRandom(int seed)
|
||||||
{
|
{
|
||||||
_x = (uint)seed;
|
x = (uint)seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FastRandom()
|
public FastRandom()
|
||||||
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
|
|||||||
/// <returns>The random value.</returns>
|
/// <returns>The random value.</returns>
|
||||||
public uint NextUInt()
|
public uint NextUInt()
|
||||||
{
|
{
|
||||||
uint t = _x ^ (_x << 11);
|
uint t = x ^ (x << 11);
|
||||||
_x = _y;
|
x = y;
|
||||||
_y = _z;
|
y = z;
|
||||||
_z = _w;
|
z = w;
|
||||||
return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
|
return w = w ^ (w >> 19) ^ t ^ (t >> 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -116,7 +116,23 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public double Duration => EndTime - StartTime;
|
public double Duration => EndTime - StartTime;
|
||||||
|
|
||||||
public SliderPath Path { get; set; }
|
private readonly SliderPath path = new SliderPath();
|
||||||
|
|
||||||
|
public SliderPath Path
|
||||||
|
{
|
||||||
|
get => path;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
path.ControlPoints.Clear();
|
||||||
|
path.ExpectedDistance.Value = null;
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(value.ControlPoints);
|
||||||
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public double Distance => Path.Distance;
|
public double Distance => Path.Distance;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
|
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.931145117263422, "diffcalc-test")]
|
[TestCase(6.9311451172608853d, "diffcalc-test")]
|
||||||
[TestCase(1.0736587013228804d, "zero-length-sliders")]
|
[TestCase(1.0736587013228804d, "zero-length-sliders")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
AddStep($"move mouse to control point {index}", () =>
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
{
|
{
|
||||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index];
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
||||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
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.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -20,11 +21,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
public class PathControlPointPiece : BlueprintPiece<Slider>
|
public class PathControlPointPiece : BlueprintPiece<Slider>
|
||||||
{
|
{
|
||||||
public Action<int, MouseButtonEvent> RequestSelection;
|
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
|
||||||
|
|
||||||
public readonly BindableBool IsSelected = new BindableBool();
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
public readonly int Index;
|
|
||||||
|
public readonly PathControlPoint ControlPoint;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
@ -37,10 +38,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public PathControlPointPiece(Slider slider, int index)
|
private IBindable<Vector2> sliderPosition;
|
||||||
|
private IBindable<int> pathVersion;
|
||||||
|
|
||||||
|
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
Index = index;
|
|
||||||
|
ControlPoint = controlPoint;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -86,48 +91,41 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.LoadComplete();
|
||||||
|
|
||||||
Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
|
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
||||||
|
sliderPosition.BindValueChanged(_ => updateDisplay());
|
||||||
|
|
||||||
|
pathVersion = slider.Path.Version.GetBoundCopy();
|
||||||
|
pathVersion.BindValueChanged(_ => updateDisplay());
|
||||||
|
|
||||||
|
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplay()
|
||||||
|
{
|
||||||
updateMarkerDisplay();
|
updateMarkerDisplay();
|
||||||
updateConnectingPath();
|
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();
|
|
||||||
|
|
||||||
if (Index != slider.Path.ControlPoints.Length - 1)
|
|
||||||
{
|
|
||||||
path.AddVertex(Vector2.Zero);
|
|
||||||
path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The connecting path is excluded from positional input
|
// 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 OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateMarkerDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
if (RequestSelection == null)
|
if (RequestSelection == null)
|
||||||
@ -136,12 +134,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
RequestSelection.Invoke(Index, e);
|
RequestSelection.Invoke(this, e);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
if (!IsSelected.Value)
|
if (!IsSelected.Value)
|
||||||
RequestSelection.Invoke(Index, e);
|
RequestSelection.Invoke(this, e);
|
||||||
return false; // Allow context menu to show
|
return false; // Allow context menu to show
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,9 +154,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override bool OnDrag(DragEvent e)
|
protected override bool OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||||
|
|
||||||
if (Index == 0)
|
|
||||||
{
|
{
|
||||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||||
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
||||||
@ -168,29 +164,51 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
slider.StartTime = snappedTime;
|
slider.StartTime = snappedTime;
|
||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
for (int i = 1; i < newControlPoints.Length; i++)
|
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||||
newControlPoints[i] -= movementDelta;
|
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
newControlPoints[Index] += e.Delta;
|
ControlPoint.Position.Value += e.Delta;
|
||||||
|
|
||||||
if (isSegmentSeparatorWithNext)
|
|
||||||
newControlPoints[Index + 1] = newControlPoints[Index];
|
|
||||||
|
|
||||||
if (isSegmentSeparatorWithPrevious)
|
|
||||||
newControlPoints[Index - 1] = newControlPoints[Index];
|
|
||||||
|
|
||||||
ControlPointsChanged?.Invoke(newControlPoints);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnDragEnd(DragEndEvent e) => true;
|
protected override bool OnDragEnd(DragEndEvent e) => true;
|
||||||
|
|
||||||
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
/// <summary>
|
||||||
|
/// Updates the state of the circular control point marker.
|
||||||
|
/// </summary>
|
||||||
|
private void updateMarkerDisplay()
|
||||||
|
{
|
||||||
|
Position = slider.StackedPosition + ControlPoint.Position.Value;
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
|
Color4 colour = ControlPoint.Type.Value != null ? 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();
|
||||||
|
|
||||||
|
int index = slider.Path.ControlPoints.IndexOf(ControlPoint);
|
||||||
|
|
||||||
|
if (index == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (++index != slider.Path.ControlPoints.Count)
|
||||||
|
{
|
||||||
|
path.AddVertex(Vector2.Zero);
|
||||||
|
path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
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.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
@ -14,6 +15,8 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -23,10 +26,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
|
||||||
|
|
||||||
internal readonly Container<PathControlPointPiece> Pieces;
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
private readonly bool allowSelection;
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
@ -34,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPlacementHandler placementHandler { get; set; }
|
private IPlacementHandler placementHandler { get; set; }
|
||||||
|
|
||||||
|
private IBindableList<PathControlPoint> controlPoints;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
@ -49,33 +54,40 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
inputManager = GetContainingInputManager();
|
inputManager = GetContainingInputManager();
|
||||||
|
|
||||||
|
controlPoints = slider.Path.ControlPoints.GetBoundCopy();
|
||||||
|
controlPoints.ItemsAdded += addControlPoints;
|
||||||
|
controlPoints.ItemsRemoved += removeControlPoints;
|
||||||
|
|
||||||
|
addControlPoints(controlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
private void addControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
||||||
{
|
{
|
||||||
base.Update();
|
foreach (var point in controlPoints)
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
|
||||||
{
|
{
|
||||||
var piece = new PathControlPointPiece(slider, Pieces.Count)
|
var piece = new PathControlPointPiece(slider, point);
|
||||||
{
|
|
||||||
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (allowSelection)
|
if (allowSelection)
|
||||||
piece.RequestSelection = selectPiece;
|
piece.RequestSelection = selectPiece;
|
||||||
|
|
||||||
Pieces.Add(piece);
|
Pieces.Add(piece);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
private void removeControlPoints(IEnumerable<PathControlPoint> controlPoints)
|
||||||
Pieces.Remove(Pieces[Pieces.Count - 1]);
|
{
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
Pieces.RemoveAll(p => p.ControlPoint == point);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
foreach (var piece in Pieces)
|
foreach (var piece in Pieces)
|
||||||
|
{
|
||||||
piece.IsSelected.Value = false;
|
piece.IsSelected.Value = false;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,51 +104,53 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
||||||
|
|
||||||
private void selectPiece(int index, MouseButtonEvent e)
|
private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
||||||
Pieces[index].IsSelected.Toggle();
|
piece.IsSelected.Toggle();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var piece in Pieces)
|
foreach (var p in Pieces)
|
||||||
piece.IsSelected.Value = piece.Index == index;
|
p.IsSelected.Value = p == piece;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool deleteSelected()
|
private bool deleteSelected()
|
||||||
{
|
{
|
||||||
var newControlPoints = new List<Vector2>();
|
List<PathControlPoint> toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
|
||||||
|
|
||||||
foreach (var piece in Pieces)
|
|
||||||
{
|
|
||||||
if (!piece.IsSelected.Value)
|
|
||||||
newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that there are any points to be deleted
|
// Ensure that there are any points to be deleted
|
||||||
if (newControlPoints.Count == slider.Path.ControlPoints.Length)
|
if (toRemove.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If there are 0 remaining control points, treat the slider as being deleted
|
foreach (var c in toRemove)
|
||||||
if (newControlPoints.Count == 0)
|
{
|
||||||
|
// The first control point in the slider must have a type, so take it from the previous "first" one
|
||||||
|
// Todo: Should be handled within SliderPath itself
|
||||||
|
if (c == slider.Path.ControlPoints[0] && slider.Path.ControlPoints.Count > 1 && slider.Path.ControlPoints[1].Type.Value == null)
|
||||||
|
slider.Path.ControlPoints[1].Type.Value = slider.Path.ControlPoints[0].Type.Value;
|
||||||
|
|
||||||
|
slider.Path.ControlPoints.Remove(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||||
|
if (slider.Path.ControlPoints.Count <= 1)
|
||||||
{
|
{
|
||||||
placementHandler?.Delete(slider);
|
placementHandler?.Delete(slider);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make control points relative
|
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
|
||||||
Vector2 first = newControlPoints[0];
|
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
||||||
for (int i = 0; i < newControlPoints.Count; i++)
|
Vector2 first = slider.Path.ControlPoints[0].Position.Value;
|
||||||
newControlPoints[i] = newControlPoints[i] - first;
|
foreach (var c in slider.Path.ControlPoints)
|
||||||
|
c.Position.Value -= first;
|
||||||
// The slider's position defines the position of the first control point, and all further control points are relative to that point
|
|
||||||
slider.Position += first;
|
slider.Position += first;
|
||||||
|
|
||||||
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
foreach (var piece in Pieces)
|
foreach (var piece in Pieces)
|
||||||
piece.IsSelected.Value = false;
|
piece.IsSelected.Value = false;
|
||||||
|
|
||||||
ControlPointsChanged?.Invoke(newControlPoints.ToArray());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,16 +161,63 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
if (!Pieces.Any(p => p.IsHovered))
|
if (!Pieces.Any(p => p.IsHovered))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
|
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
|
||||||
|
int count = selectedPieces.Count;
|
||||||
|
|
||||||
if (selectedPoints == 0)
|
if (count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
List<MenuItem> items = new List<MenuItem>();
|
||||||
|
|
||||||
|
if (!selectedPieces.Contains(Pieces[0]))
|
||||||
|
items.Add(createMenuItemForPathType(null));
|
||||||
|
|
||||||
|
// todo: hide/disable items which aren't valid for selected points
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Linear));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.PerfectCurve));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Bezier));
|
||||||
|
items.Add(createMenuItemForPathType(PathType.Catmull));
|
||||||
|
|
||||||
return new MenuItem[]
|
return new MenuItem[]
|
||||||
{
|
{
|
||||||
new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected())
|
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()),
|
||||||
|
new OsuMenuItem("Curve type")
|
||||||
|
{
|
||||||
|
Items = items
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MenuItem createMenuItemForPathType(PathType? type)
|
||||||
|
{
|
||||||
|
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||||
|
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
|
||||||
|
|
||||||
|
var item = new PathTypeMenuItem(type, () =>
|
||||||
|
{
|
||||||
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
|
p.ControlPoint.Type.Value = type;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countOfState == totalCount)
|
||||||
|
item.State.Value = TernaryState.True;
|
||||||
|
else if (countOfState > 0)
|
||||||
|
item.State.Value = TernaryState.Indeterminate;
|
||||||
|
else
|
||||||
|
item.State.Value = TernaryState.False;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PathTypeMenuItem : TernaryStateMenuItem
|
||||||
|
{
|
||||||
|
public PathTypeMenuItem(PathType? type, Action action)
|
||||||
|
: base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TernaryState changeState(TernaryState state) => TernaryState.True;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -27,11 +24,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private HitCirclePiece headCirclePiece;
|
private HitCirclePiece headCirclePiece;
|
||||||
private HitCirclePiece tailCirclePiece;
|
private HitCirclePiece tailCirclePiece;
|
||||||
|
|
||||||
private readonly List<Segment> segments = new List<Segment>();
|
|
||||||
private Vector2 cursor;
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
private PlacementState state;
|
private PlacementState state;
|
||||||
|
private PathControlPoint segmentStart;
|
||||||
|
private PathControlPoint cursor;
|
||||||
|
private int currentSegmentLength;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private HitObjectComposer composer { get; set; }
|
private HitObjectComposer composer { get; set; }
|
||||||
@ -40,7 +38,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
: base(new Objects.Slider())
|
: base(new Objects.Slider())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
segments.Add(new Segment(Vector2.Zero));
|
|
||||||
|
HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear));
|
||||||
|
currentSegmentLength = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -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, false) { ControlPointsChanged = _ => updateSlider() },
|
new PathControlPointVisualiser(HitObject, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
@ -72,9 +72,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
|
ensureCursor();
|
||||||
|
|
||||||
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
||||||
// is used instead since snapping control points doesn't make much sense
|
// is used instead since snapping control points doesn't make much sense
|
||||||
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +93,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
segments.Last().ControlPoints.Add(cursor);
|
ensureCursor();
|
||||||
|
|
||||||
|
// Detatch the cursor
|
||||||
|
cursor = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +115,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
protected override bool OnDoubleClick(DoubleClickEvent e)
|
protected override bool OnDoubleClick(DoubleClickEvent e)
|
||||||
{
|
{
|
||||||
segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last()));
|
// Todo: This should all not occur on double click, but rather if the previous control point is hovered.
|
||||||
|
segmentStart = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1];
|
||||||
|
segmentStart.Type.Value = PathType.Linear;
|
||||||
|
|
||||||
|
currentSegmentLength = 1;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,14 +141,39 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
updateSlider();
|
updateSlider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePathType()
|
||||||
|
{
|
||||||
|
switch (currentSegmentLength)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
segmentStart.Type.Value = PathType.Linear;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
segmentStart.Type.Value = PathType.PerfectCurve;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
segmentStart.Type.Value = PathType.Bezier;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCursor()
|
||||||
|
{
|
||||||
|
if (cursor == null)
|
||||||
|
{
|
||||||
|
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } });
|
||||||
|
currentSegmentLength++;
|
||||||
|
|
||||||
|
updatePathType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
|
|
||||||
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
|
|
||||||
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
|
||||||
|
|
||||||
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
|
|
||||||
|
|
||||||
bodyPiece.UpdateFrom(HitObject);
|
bodyPiece.UpdateFrom(HitObject);
|
||||||
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||||
@ -156,15 +190,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
Initial,
|
Initial,
|
||||||
Body,
|
Body,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Segment
|
|
||||||
{
|
|
||||||
public readonly List<Vector2> ControlPoints = new List<Vector2>();
|
|
||||||
|
|
||||||
public Segment(Vector2 offset)
|
|
||||||
{
|
|
||||||
ControlPoints.Add(offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -11,7 +11,6 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -40,10 +39,20 @@ 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),
|
||||||
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
|
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IBindable<int> pathVersion;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
pathVersion = HitObject.Path.Version.GetBoundCopy();
|
||||||
|
pathVersion.BindValueChanged(_ => updatePath());
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -77,12 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
Debug.Assert(placementControlPointIndex != null);
|
Debug.Assert(placementControlPointIndex != null);
|
||||||
|
|
||||||
Vector2 position = e.MousePosition - HitObject.Position;
|
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position;
|
||||||
|
|
||||||
var controlPoints = HitObject.Path.ControlPoints.ToArray();
|
|
||||||
controlPoints[placementControlPointIndex.Value] = position;
|
|
||||||
|
|
||||||
onNewControlPoints(controlPoints);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -97,15 +101,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
position -= HitObject.Position;
|
position -= HitObject.Position;
|
||||||
|
|
||||||
var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1];
|
|
||||||
HitObject.Path.ControlPoints.CopyTo(controlPoints);
|
|
||||||
|
|
||||||
int insertionIndex = 0;
|
int insertionIndex = 0;
|
||||||
float minDistance = float.MaxValue;
|
float minDistance = float.MaxValue;
|
||||||
|
|
||||||
for (int i = 0; i < controlPoints.Length - 2; i++)
|
for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++)
|
||||||
{
|
{
|
||||||
float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position);
|
float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position);
|
||||||
|
|
||||||
if (dist < minDistance)
|
if (dist < minDistance)
|
||||||
{
|
{
|
||||||
@ -115,21 +116,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
// Move the control points from the insertion index onwards to make room for the insertion
|
||||||
Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1);
|
HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } });
|
||||||
controlPoints[insertionIndex] = position;
|
|
||||||
|
|
||||||
onNewControlPoints(controlPoints);
|
|
||||||
|
|
||||||
return insertionIndex;
|
return insertionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewControlPoints(Vector2[] controlPoints)
|
private void updatePath()
|
||||||
{
|
{
|
||||||
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
|
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
|
||||||
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
|
||||||
|
|
||||||
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
|
|
||||||
|
|
||||||
UpdateHitObject();
|
UpdateHitObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
{
|
{
|
||||||
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
|
||||||
: base(hitObject, nextHitObject, hitObject.StackedEndPosition)
|
: base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
|
@ -28,11 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
|
||||||
var newControlPoints = new Vector2[slider.Path.ControlPoints.Length];
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Length; i++)
|
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
||||||
newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y);
|
|
||||||
|
|
||||||
slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private OsuRulesetConfigManager config { get; set; }
|
private OsuRulesetConfigManager config { get; set; }
|
||||||
@ -84,9 +84,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||||
scaleBindable.BindTo(HitObject.ScaleBindable);
|
scaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
pathBindable.BindValueChanged(_ => Body.Refresh());
|
pathVersion.BindValueChanged(_ => Body.Refresh());
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public class DrawableSliderHead : DrawableHitCircle
|
public class DrawableSliderHead : DrawableHitCircle
|
||||||
{
|
{
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
|
||||||
@ -27,10 +26,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
positionBindable.BindValueChanged(_ => updatePosition());
|
positionBindable.BindValueChanged(_ => updatePosition());
|
||||||
pathBindable.BindValueChanged(_ => updatePosition(), true);
|
pathVersion.BindValueChanged(_ => updatePosition(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public bool Tracking { get; set; }
|
public bool Tracking { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
|
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
|
||||||
: base(hitCircle)
|
: base(hitCircle)
|
||||||
@ -36,10 +35,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
AlwaysPresent = true;
|
AlwaysPresent = true;
|
||||||
|
|
||||||
positionBindable.BindTo(hitCircle.PositionBindable);
|
positionBindable.BindTo(hitCircle.PositionBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
|
|
||||||
positionBindable.BindValueChanged(_ => updatePosition());
|
positionBindable.BindValueChanged(_ => updatePosition());
|
||||||
pathBindable.BindValueChanged(_ => updatePosition(), true);
|
pathVersion.BindValueChanged(_ => updatePosition(), true);
|
||||||
|
|
||||||
// TODO: This has no drawable content. Support for skins should be added.
|
// TODO: This has no drawable content. Support for skins should be added.
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Types;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -28,17 +27,21 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
|
||||||
|
|
||||||
public readonly Bindable<SliderPath> PathBindable = new Bindable<SliderPath>();
|
private readonly SliderPath path = new SliderPath();
|
||||||
|
|
||||||
public SliderPath Path
|
public SliderPath Path
|
||||||
{
|
{
|
||||||
get => PathBindable.Value;
|
get => path;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
PathBindable.Value = value;
|
path.ControlPoints.Clear();
|
||||||
endPositionCache.Invalidate();
|
path.ExpectedDistance.Value = null;
|
||||||
|
|
||||||
updateNestedPositions();
|
if (value != null)
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(value.ControlPoints);
|
||||||
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +53,6 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
base.Position = value;
|
base.Position = value;
|
||||||
endPositionCache.Invalidate();
|
|
||||||
|
|
||||||
updateNestedPositions();
|
updateNestedPositions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
{
|
{
|
||||||
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
|
SamplesBindable.ItemsAdded += _ => updateNestedSamples();
|
||||||
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
|
SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
|
||||||
|
Path.Version.ValueChanged += _ => updateNestedPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
@ -189,6 +191,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
private void updateNestedPositions()
|
private void updateNestedPositions()
|
||||||
{
|
{
|
||||||
|
endPositionCache.Invalidate();
|
||||||
|
|
||||||
if (HeadCircle != null)
|
if (HeadCircle != null)
|
||||||
HeadCircle.Position = Position;
|
HeadCircle.Position = Position;
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SliderTailCircle : SliderCircle
|
public class SliderTailCircle : SliderCircle
|
||||||
{
|
{
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
public SliderTailCircle(Slider slider)
|
public SliderTailCircle(Slider slider)
|
||||||
{
|
{
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathVersion.BindTo(slider.Path.Version);
|
||||||
pathBindable.BindValueChanged(_ => Position = slider.EndPosition);
|
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
|
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
|
||||||
|
@ -3,14 +3,16 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning
|
namespace osu.Game.Rulesets.Osu.Skinning
|
||||||
{
|
{
|
||||||
public class LegacyCursor : CompositeDrawable
|
public class LegacyCursor : OsuCursorSprite
|
||||||
{
|
{
|
||||||
|
private bool spin;
|
||||||
|
|
||||||
public LegacyCursor()
|
public LegacyCursor()
|
||||||
{
|
{
|
||||||
Size = new Vector2(50);
|
Size = new Vector2(50);
|
||||||
@ -22,7 +24,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
new NonPlayfieldSprite
|
new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
@ -30,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
new NonPlayfieldSprite
|
ExpandTarget = new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursor"),
|
Texture = skin.GetTexture("cursor"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -38,5 +42,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
if (spin)
|
||||||
|
ExpandTarget.Spin(10000, RotationDirection.Clockwise);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
AllowSliderBallTint,
|
AllowSliderBallTint,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
|
CursorRotate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
private bool cursorExpand;
|
private bool cursorExpand;
|
||||||
|
|
||||||
private Container expandTarget;
|
private SkinnableDrawable cursorSprite;
|
||||||
|
|
||||||
|
private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite;
|
||||||
|
|
||||||
public OsuCursor()
|
public OsuCursor()
|
||||||
{
|
{
|
||||||
@ -37,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = expandTarget = new Container
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
|
Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
|
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
|
||||||
|
|
||||||
private class DefaultCursor : CompositeDrawable
|
private class DefaultCursor : OsuCursorSprite
|
||||||
{
|
{
|
||||||
public DefaultCursor()
|
public DefaultCursor()
|
||||||
{
|
{
|
||||||
@ -71,10 +73,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
ExpandTarget = new CircularContainer
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
BorderThickness = size / 6,
|
BorderThickness = size / 6,
|
||||||
|
17
osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs
Normal file
17
osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||||
|
{
|
||||||
|
public abstract class OsuCursorSprite : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The an optional piece of the cursor to expand when in a clicked state.
|
||||||
|
/// If null, the whole cursor will be affected by expansion.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable ExpandTarget { get; protected set; }
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
@ -44,7 +43,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.SlateGray
|
Colour = Color4.SlateGray
|
||||||
},
|
},
|
||||||
new TestDistanceSnapGrid(new HitObject(), grid_position)
|
new TestDistanceSnapGrid()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.SlateGray
|
Colour = Color4.SlateGray
|
||||||
},
|
},
|
||||||
new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
|
new TestDistanceSnapGrid(100)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -82,68 +81,68 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
{
|
{
|
||||||
public new float DistanceSpacing => base.DistanceSpacing;
|
public new float DistanceSpacing => base.DistanceSpacing;
|
||||||
|
|
||||||
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
|
public TestDistanceSnapGrid(double? endTime = null)
|
||||||
: base(hitObject, nextHitObject, centrePosition)
|
: base(grid_position, 0, endTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateContent(Vector2 centrePosition)
|
protected override void CreateContent(Vector2 startPosition)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(5),
|
Size = new Vector2(5),
|
||||||
Position = centrePosition
|
Position = startPosition
|
||||||
});
|
});
|
||||||
|
|
||||||
int beatIndex = 0;
|
int beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(5, 10),
|
Size = new Vector2(5, 10),
|
||||||
Position = new Vector2(s, centrePosition.Y),
|
Position = new Vector2(s, startPosition.Y),
|
||||||
Colour = GetColourForBeatIndex(beatIndex)
|
Colour = GetColourForBeatIndex(beatIndex)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(5, 10),
|
Size = new Vector2(5, 10),
|
||||||
Position = new Vector2(s, centrePosition.Y),
|
Position = new Vector2(s, startPosition.Y),
|
||||||
Colour = GetColourForBeatIndex(beatIndex)
|
Colour = GetColourForBeatIndex(beatIndex)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(10, 5),
|
Size = new Vector2(10, 5),
|
||||||
Position = new Vector2(centrePosition.X, s),
|
Position = new Vector2(startPosition.X, s),
|
||||||
Colour = GetColourForBeatIndex(beatIndex)
|
Colour = GetColourForBeatIndex(beatIndex)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(10, 5),
|
Size = new Vector2(10, 5),
|
||||||
Position = new Vector2(centrePosition.X, s),
|
Position = new Vector2(startPosition.X, s),
|
||||||
Colour = GetColourForBeatIndex(beatIndex)
|
Colour = GetColourForBeatIndex(beatIndex)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -25,6 +27,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
{
|
{
|
||||||
typeof(TimelineArea),
|
typeof(TimelineArea),
|
||||||
|
typeof(TimelineHitObjectDisplay),
|
||||||
typeof(Timeline),
|
typeof(Timeline),
|
||||||
typeof(TimelineButton),
|
typeof(TimelineButton),
|
||||||
typeof(CentreMarker)
|
typeof(CentreMarker)
|
||||||
@ -35,6 +38,8 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
{
|
{
|
||||||
Beatmap.Value = new WaveformTestBeatmap(audio);
|
Beatmap.Value = new WaveformTestBeatmap(audio);
|
||||||
|
|
||||||
|
var editorBeatmap = new EditorBeatmap<HitObject>((Beatmap<HitObject>)Beatmap.Value.Beatmap);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new FillFlowContainer
|
||||||
@ -50,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
},
|
},
|
||||||
new TimelineArea
|
new TimelineArea
|
||||||
{
|
{
|
||||||
|
Child = new TimelineHitObjectDisplay(editorBeatmap),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
@ -285,8 +285,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
protected class PausePlayer : TestPlayer
|
protected class PausePlayer : TestPlayer
|
||||||
{
|
{
|
||||||
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
|
||||||
|
|
||||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||||
|
|
||||||
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
public new HUDOverlay HUDOverlay => base.HUDOverlay;
|
||||||
|
51
osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
Normal file
51
osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
|
||||||
|
public class TestScenePauseWhenInactive : PlayerTestScene
|
||||||
|
{
|
||||||
|
protected new TestPlayer Player => (TestPlayer)base.Player;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
|
||||||
|
|
||||||
|
beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
|
public TestScenePauseWhenInactive()
|
||||||
|
: base(new OsuRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDoesntPauseDuringIntro()
|
||||||
|
{
|
||||||
|
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
|
||||||
|
|
||||||
|
AddStep("resume player", () => Player.GameplayClockContainer.Start());
|
||||||
|
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
|
||||||
|
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||||
|
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
|
||||||
|
}
|
||||||
|
}
|
@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
beforeLoadAction?.Invoke();
|
beforeLoadAction?.Invoke();
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToClock>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToClock(Beatmap.Value.Track);
|
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||||
|
|
||||||
InputManager.Child = container = new TestPlayerLoaderContainer(
|
InputManager.Child = container = new TestPlayerLoaderContainer(
|
||||||
loader = new TestPlayerLoader(() =>
|
loader = new TestPlayerLoader(() =>
|
||||||
|
193
osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs
Normal file
193
osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Lines;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneSliderPath : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly SmoothPath drawablePath;
|
||||||
|
private SliderPath path;
|
||||||
|
|
||||||
|
public TestSceneSliderPath()
|
||||||
|
{
|
||||||
|
Child = drawablePath = new SmoothPath
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
path = new SliderPath();
|
||||||
|
});
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (path != null)
|
||||||
|
{
|
||||||
|
List<Vector2> vertices = new List<Vector2>();
|
||||||
|
path.GetPathToProgress(vertices, 0, 1);
|
||||||
|
|
||||||
|
drawablePath.Vertices = vertices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyPath()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(PathType.Linear)]
|
||||||
|
[TestCase(PathType.Bezier)]
|
||||||
|
[TestCase(PathType.Catmull)]
|
||||||
|
[TestCase(PathType.PerfectCurve)]
|
||||||
|
public void TestSingleSegment(PathType type)
|
||||||
|
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
|
||||||
|
[TestCase(PathType.Linear)]
|
||||||
|
[TestCase(PathType.Bezier)]
|
||||||
|
[TestCase(PathType.Catmull)]
|
||||||
|
[TestCase(PathType.PerfectCurve)]
|
||||||
|
public void TestMultipleSegment(PathType type)
|
||||||
|
{
|
||||||
|
AddStep("create path", () =>
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero));
|
||||||
|
path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddControlPoint()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100))));
|
||||||
|
AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInsertControlPoint()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100))));
|
||||||
|
AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveControlPoint()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangePathType()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddSegmentByChangingType()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))));
|
||||||
|
AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveSegmentByChangingType()
|
||||||
|
{
|
||||||
|
AddStep("create path", () =>
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
|
||||||
|
path.ControlPoints[1].Type.Value = PathType.Bezier;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveSegmentByRemovingControlPoint()
|
||||||
|
{
|
||||||
|
AddStep("create path", () =>
|
||||||
|
{
|
||||||
|
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
|
||||||
|
path.ControlPoints[1].Type.Value = PathType.Bezier;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(2)]
|
||||||
|
[TestCase(4)]
|
||||||
|
public void TestPerfectCurveFallbackScenarios(int points)
|
||||||
|
{
|
||||||
|
AddStep("create path", () =>
|
||||||
|
{
|
||||||
|
switch (points)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLengthenLastSegment()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortenLastSegment()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortenFirstSegment()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortenToZeroLength()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShortenToNegativeLength()
|
||||||
|
{
|
||||||
|
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
|
||||||
|
AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PathControlPoint> createSegment(PathType type, params Vector2[] controlPoints)
|
||||||
|
{
|
||||||
|
var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList();
|
||||||
|
points[0].Type.Value = type;
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs
Normal file
65
osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneSettingsSource : OsuTestScene
|
||||||
|
{
|
||||||
|
public TestSceneSettingsSource()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Width = 0.5f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Padding = new MarginPadding(50),
|
||||||
|
ChildrenEnumerable = new TestTargetClass().CreateSettingsControls()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTargetClass
|
||||||
|
{
|
||||||
|
[SettingSource("Sample bool", "Clicking this changes a setting")]
|
||||||
|
public BindableBool TickBindable { get; } = new BindableBool();
|
||||||
|
|
||||||
|
[SettingSource("Sample float", "Change something for a mod")]
|
||||||
|
public BindableFloat SliderBindable { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Default = 5,
|
||||||
|
Value = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Sample enum", "Change something for a mod")]
|
||||||
|
public Bindable<TestEnum> EnumBindable { get; } = new Bindable<TestEnum>
|
||||||
|
{
|
||||||
|
Default = TestEnum.Value1,
|
||||||
|
Value = TestEnum.Value2
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Sample string", "Change something for a mod")]
|
||||||
|
public Bindable<string> StringBindable { get; } = new Bindable<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TestEnum
|
||||||
|
{
|
||||||
|
Value1,
|
||||||
|
Value2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,6 +95,42 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
|
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoFilterOnSimpleResume()
|
||||||
|
{
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
||||||
|
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddStep("return", () => songSelect.MakeCurrent());
|
||||||
|
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
|
||||||
|
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFilterOnResumeAfterChange()
|
||||||
|
{
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
|
||||||
|
AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
|
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
|
||||||
|
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
|
||||||
|
|
||||||
|
AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
|
||||||
|
|
||||||
|
AddStep("return", () => songSelect.MakeCurrent());
|
||||||
|
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
|
||||||
|
AddAssert("filter count is 2", () => songSelect.FilterCount == 2);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAudioResuming()
|
public void TestAudioResuming()
|
||||||
{
|
{
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
AddAssert("Parallax is off", () => stack.ParallaxAmount == 0);
|
AddAssert("Parallax is off", () => stack.ParallaxAmount == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestScreen : ScreenWithBeatmapBackground
|
public class TestScreen : ScreenWithBeatmapBackground
|
||||||
{
|
{
|
||||||
private readonly string screenText;
|
private readonly string screenText;
|
||||||
|
|
||||||
|
107
osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
Normal file
107
osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneModSettings : OsuTestScene
|
||||||
|
{
|
||||||
|
private TestModSelectOverlay modSelect;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Add(modSelect = new TestModSelectOverlay
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
});
|
||||||
|
|
||||||
|
var testMod = new TestModCustomisable1();
|
||||||
|
|
||||||
|
AddStep("open", modSelect.Show);
|
||||||
|
AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value);
|
||||||
|
AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
|
||||||
|
AddStep("select mod", () => modSelect.SelectMod(testMod));
|
||||||
|
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
|
||||||
|
AddStep("open Customisation", () => modSelect.CustomiseButton.Click());
|
||||||
|
AddStep("deselect mod", () => modSelect.SelectMod(testMod));
|
||||||
|
AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModSelectOverlay : ModSelectOverlay
|
||||||
|
{
|
||||||
|
public new Container ModSettingsContainer => base.ModSettingsContainer;
|
||||||
|
public new TriangleButton CustomiseButton => base.CustomiseButton;
|
||||||
|
|
||||||
|
public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
|
||||||
|
|
||||||
|
public void SelectMod(Mod mod) =>
|
||||||
|
ModSectionsContainer.Children.Single(s => s.ModType == mod.Type)
|
||||||
|
.ButtonsContainer.OfType<ModButton>().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1);
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
foreach (var section in ModSectionsContainer)
|
||||||
|
{
|
||||||
|
if (section.ModType == ModType.Conversion)
|
||||||
|
{
|
||||||
|
section.Mods = new Mod[]
|
||||||
|
{
|
||||||
|
new TestModCustomisable1(),
|
||||||
|
new TestModCustomisable2()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
section.Mods = Array.Empty<Mod>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModCustomisable1 : TestModCustomisable
|
||||||
|
{
|
||||||
|
public override string Name => "Customisable Mod 1";
|
||||||
|
|
||||||
|
public override string Acronym => "CM1";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestModCustomisable2 : TestModCustomisable
|
||||||
|
{
|
||||||
|
public override string Name => "Customisable Mod 2";
|
||||||
|
|
||||||
|
public override string Acronym => "CM2";
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class TestModCustomisable : Mod, IApplicableMod
|
||||||
|
{
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
[SettingSource("Sample float", "Change something for a mod")]
|
||||||
|
public BindableFloat SliderBindable { get; } = new BindableFloat
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Default = 5,
|
||||||
|
Value = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
[SettingSource("Sample bool", "Clicking this changes a setting")]
|
||||||
|
public BindableBool TickBindable { get; } = new BindableBool();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
@ -11,13 +14,22 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestScenePopupDialog : OsuTestScene
|
public class TestScenePopupDialog : OsuTestScene
|
||||||
{
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(PopupDialogOkButton),
|
||||||
|
typeof(PopupDialogCancelButton),
|
||||||
|
typeof(PopupDialogButton),
|
||||||
|
typeof(DialogButton),
|
||||||
|
};
|
||||||
|
|
||||||
public TestScenePopupDialog()
|
public TestScenePopupDialog()
|
||||||
{
|
{
|
||||||
Add(new TestPopupDialog
|
AddStep("new popup", () =>
|
||||||
{
|
Add(new TestPopupDialog
|
||||||
RelativeSizeAxes = Axes.Both,
|
{
|
||||||
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
|
RelativeSizeAxes = Axes.Both,
|
||||||
});
|
State = { Value = Framework.Graphics.Containers.Visibility.Visible },
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestPopupDialog : PopupDialog
|
private class TestPopupDialog : PopupDialog
|
||||||
|
@ -83,88 +83,81 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScrollState _scrollState;
|
private ScrollState scrollState;
|
||||||
|
|
||||||
private ScrollState scrollState
|
private void setScrollState(ScrollState newstate)
|
||||||
{
|
{
|
||||||
get => _scrollState;
|
if (scrollState == newstate)
|
||||||
|
return;
|
||||||
|
|
||||||
set
|
delayedStateChangeDelegate?.Cancel();
|
||||||
|
|
||||||
|
switch (scrollState = newstate)
|
||||||
{
|
{
|
||||||
if (_scrollState == value)
|
case ScrollState.Scrolling:
|
||||||
return;
|
resetSelected();
|
||||||
|
|
||||||
_scrollState = value;
|
OnScrollStarted?.Invoke();
|
||||||
|
|
||||||
delayedStateChangeDelegate?.Cancel();
|
speedTo(1000f, 200);
|
||||||
|
tracker.FadeOut(100);
|
||||||
|
break;
|
||||||
|
|
||||||
switch (value)
|
case ScrollState.Stopping:
|
||||||
{
|
speedTo(0f, 2000);
|
||||||
case ScrollState.Scrolling:
|
tracker.FadeIn(200);
|
||||||
resetSelected();
|
|
||||||
|
|
||||||
OnScrollStarted?.Invoke();
|
delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Stopped), 2300);
|
||||||
|
break;
|
||||||
|
|
||||||
speedTo(1000f, 200);
|
case ScrollState.Stopped:
|
||||||
tracker.FadeOut(100);
|
// Find closest to center
|
||||||
|
if (!Children.Any())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ScrollState.Stopping:
|
ScrollingTeam closest = null;
|
||||||
speedTo(0f, 2000);
|
|
||||||
tracker.FadeIn(200);
|
|
||||||
|
|
||||||
delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300);
|
foreach (var c in Children)
|
||||||
break;
|
{
|
||||||
|
if (!(c is ScrollingTeam stc))
|
||||||
|
continue;
|
||||||
|
|
||||||
case ScrollState.Stopped:
|
if (closest == null)
|
||||||
// Find closest to center
|
|
||||||
if (!Children.Any())
|
|
||||||
break;
|
|
||||||
|
|
||||||
ScrollingTeam closest = null;
|
|
||||||
|
|
||||||
foreach (var c in Children)
|
|
||||||
{
|
{
|
||||||
if (!(c is ScrollingTeam stc))
|
closest = stc;
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (closest == null)
|
|
||||||
{
|
|
||||||
closest = stc;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f);
|
|
||||||
float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f);
|
|
||||||
|
|
||||||
if (o < lastOffset)
|
|
||||||
closest = stc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Assert(closest != null, "closest != null");
|
float o = Math.Abs(c.Position.X + c.DrawWidth / 2f - DrawWidth / 2f);
|
||||||
|
float lastOffset = Math.Abs(closest.Position.X + closest.DrawWidth / 2f - DrawWidth / 2f);
|
||||||
|
|
||||||
// ReSharper disable once PossibleNullReferenceException
|
if (o < lastOffset)
|
||||||
offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f);
|
closest = stc;
|
||||||
|
}
|
||||||
|
|
||||||
ScrollingTeam st = closest;
|
Trace.Assert(closest != null, "closest != null");
|
||||||
|
|
||||||
availableTeams.RemoveAll(at => at == st.Team);
|
// ReSharper disable once PossibleNullReferenceException
|
||||||
|
offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f);
|
||||||
|
|
||||||
st.Selected = true;
|
ScrollingTeam st = closest;
|
||||||
OnSelected?.Invoke(st.Team);
|
|
||||||
|
|
||||||
delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000);
|
availableTeams.RemoveAll(at => at == st.Team);
|
||||||
break;
|
|
||||||
|
|
||||||
case ScrollState.Idle:
|
st.Selected = true;
|
||||||
resetSelected();
|
OnSelected?.Invoke(st.Team);
|
||||||
|
|
||||||
OnScrollStarted?.Invoke();
|
delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000);
|
||||||
|
break;
|
||||||
|
|
||||||
speedTo(40f, 200);
|
case ScrollState.Idle:
|
||||||
tracker.FadeOut(100);
|
resetSelected();
|
||||||
break;
|
|
||||||
}
|
OnScrollStarted?.Invoke();
|
||||||
|
|
||||||
|
speedTo(40f, 200);
|
||||||
|
tracker.FadeOut(100);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +169,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
availableTeams.Add(team);
|
availableTeams.Add(team);
|
||||||
|
|
||||||
RemoveAll(c => c is ScrollingTeam);
|
RemoveAll(c => c is ScrollingTeam);
|
||||||
scrollState = ScrollState.Idle;
|
setScrollState(ScrollState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTeams(IEnumerable<TournamentTeam> teams)
|
public void AddTeams(IEnumerable<TournamentTeam> teams)
|
||||||
@ -192,7 +185,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
{
|
{
|
||||||
availableTeams.Clear();
|
availableTeams.Clear();
|
||||||
RemoveAll(c => c is ScrollingTeam);
|
RemoveAll(c => c is ScrollingTeam);
|
||||||
scrollState = ScrollState.Idle;
|
setScrollState(ScrollState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveTeam(TournamentTeam team)
|
public void RemoveTeam(TournamentTeam team)
|
||||||
@ -217,7 +210,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
if (availableTeams.Count == 0)
|
if (availableTeams.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
scrollState = ScrollState.Scrolling;
|
setScrollState(ScrollState.Scrolling);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopScrolling()
|
public void StopScrolling()
|
||||||
@ -232,13 +225,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollState = ScrollState.Stopping;
|
setScrollState(ScrollState.Stopping);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
scrollState = ScrollState.Idle;
|
setScrollState(ScrollState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -305,7 +298,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
|
|||||||
private void speedTo(float value, double duration = 0, Easing easing = Easing.None) =>
|
private void speedTo(float value, double duration = 0, Easing easing = Easing.None) =>
|
||||||
this.TransformTo(nameof(speed), value, duration, easing);
|
this.TransformTo(nameof(speed), value, duration, easing);
|
||||||
|
|
||||||
private enum ScrollState
|
protected enum ScrollState
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
Idle,
|
Idle,
|
||||||
|
@ -9,6 +9,6 @@
|
|||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -9,8 +10,8 @@ using osu.Game.Rulesets;
|
|||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public abstract class DatabasedConfigManager<T> : ConfigManager<T>
|
public abstract class DatabasedConfigManager<TLookup> : ConfigManager<TLookup>
|
||||||
where T : struct
|
where TLookup : struct, Enum
|
||||||
{
|
{
|
||||||
private readonly SettingsStore settings;
|
private readonly SettingsStore settings;
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>();
|
private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>();
|
||||||
|
|
||||||
protected override void AddBindable<TBindable>(T lookup, Bindable<TBindable> bindable)
|
protected override void AddBindable<TBindable>(TLookup lookup, Bindable<TBindable> bindable)
|
||||||
{
|
{
|
||||||
base.AddBindable(lookup, bindable);
|
base.AddBindable(lookup, bindable);
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
namespace osu.Game.Configuration
|
||||||
{
|
{
|
||||||
public class InMemoryConfigManager<T> : ConfigManager<T>
|
public class InMemoryConfigManager<TLookup> : ConfigManager<TLookup>
|
||||||
where T : struct
|
where TLookup : struct, Enum
|
||||||
{
|
{
|
||||||
public InMemoryConfigManager()
|
public InMemoryConfigManager()
|
||||||
{
|
{
|
||||||
|
110
osu.Game/Configuration/SettingSourceAttribute.cs
Normal file
110
osu.Game/Configuration/SettingSourceAttribute.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Configuration
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An attribute to mark a bindable as being exposed to the user via settings controls.
|
||||||
|
/// Can be used in conjunction with <see cref="SettingSourceExtensions.CreateSettingsControls"/> to automatically create UI controls.
|
||||||
|
/// </summary>
|
||||||
|
[MeansImplicitUse]
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class SettingSourceAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string Label { get; }
|
||||||
|
|
||||||
|
public string Description { get; }
|
||||||
|
|
||||||
|
public SettingSourceAttribute(string label, string description = null)
|
||||||
|
{
|
||||||
|
Label = label ?? string.Empty;
|
||||||
|
Description = description ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingSourceExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<Drawable> CreateSettingsControls(this object obj)
|
||||||
|
{
|
||||||
|
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
|
||||||
|
|
||||||
|
if (attr == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var prop = property.GetValue(obj);
|
||||||
|
|
||||||
|
switch (prop)
|
||||||
|
{
|
||||||
|
case BindableNumber<float> bNumber:
|
||||||
|
yield return new SettingsSlider<float>
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
Bindable = bNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BindableNumber<double> bNumber:
|
||||||
|
yield return new SettingsSlider<double>
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
Bindable = bNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BindableNumber<int> bNumber:
|
||||||
|
yield return new SettingsSlider<int>
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
Bindable = bNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Bindable<bool> bBool:
|
||||||
|
yield return new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
Bindable = bBool
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Bindable<string> bString:
|
||||||
|
yield return new SettingsTextBox
|
||||||
|
{
|
||||||
|
LabelText = attr.Label,
|
||||||
|
Bindable = bString
|
||||||
|
};
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IBindable bindable:
|
||||||
|
var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]);
|
||||||
|
var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
|
||||||
|
|
||||||
|
dropdown.GetType().GetProperty(nameof(IHasCurrentValue<object>.Current))?.SetValue(dropdown, obj);
|
||||||
|
|
||||||
|
yield return dropdown;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({prop})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ namespace osu.Game.Graphics
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||||
public static TransformSequence<T> FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None)
|
public static TransformSequence<T> FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None)
|
||||||
where T : IHasAccentColour
|
where T : class, IHasAccentColour
|
||||||
=> accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing);
|
=> accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -20,9 +20,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public class DialogButton : OsuClickableContainer
|
public class DialogButton : OsuClickableContainer
|
||||||
{
|
{
|
||||||
|
private const float idle_width = 0.8f;
|
||||||
private const float hover_width = 0.9f;
|
private const float hover_width = 0.9f;
|
||||||
|
|
||||||
private const float hover_duration = 500;
|
private const float hover_duration = 500;
|
||||||
private const float glow_fade_duration = 250;
|
|
||||||
private const float click_duration = 200;
|
private const float click_duration = 200;
|
||||||
|
|
||||||
public readonly BindableBool Selected = new BindableBool();
|
public readonly BindableBool Selected = new BindableBool();
|
||||||
@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Width = 0.8f,
|
Width = idle_width,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
MaskingSmoothness = 2,
|
MaskingSmoothness = 2,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
@ -199,26 +200,50 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
private bool clickAnimating;
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In);
|
var flash = new Box
|
||||||
flash();
|
|
||||||
|
|
||||||
this.Delay(click_duration).Schedule(delegate
|
|
||||||
{
|
{
|
||||||
colourContainer.ResizeTo(new Vector2(0.8f, 1f));
|
RelativeSizeAxes = Axes.Both,
|
||||||
spriteText.Spacing = Vector2.Zero;
|
Colour = ButtonColour,
|
||||||
glowContainer.FadeOut();
|
Blending = BlendingParameters.Additive,
|
||||||
});
|
Alpha = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
colourContainer.Add(flash);
|
||||||
|
flash.FadeOutFromOne(100).Expire();
|
||||||
|
|
||||||
|
clickAnimating = true;
|
||||||
|
colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint)
|
||||||
|
.OnComplete(_ =>
|
||||||
|
{
|
||||||
|
clickAnimating = false;
|
||||||
|
Selected.TriggerChange();
|
||||||
|
});
|
||||||
|
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
colourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad);
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(MouseUpEvent e)
|
||||||
|
{
|
||||||
|
if (Selected.Value)
|
||||||
|
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
|
||||||
|
return base.OnMouseUp(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
base.OnHover(e);
|
base.OnHover(e);
|
||||||
|
|
||||||
Selected.Value = true;
|
Selected.Value = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,36 +255,23 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private void selectionChanged(ValueChangedEvent<bool> args)
|
private void selectionChanged(ValueChangedEvent<bool> args)
|
||||||
{
|
{
|
||||||
|
if (clickAnimating)
|
||||||
|
return;
|
||||||
|
|
||||||
if (args.NewValue)
|
if (args.NewValue)
|
||||||
{
|
{
|
||||||
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
|
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
|
||||||
colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic);
|
colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic);
|
||||||
glowContainer.FadeIn(glow_fade_duration, Easing.Out);
|
glowContainer.FadeIn(hover_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic);
|
colourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic);
|
||||||
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
|
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
|
||||||
glowContainer.FadeOut(glow_fade_duration, Easing.Out);
|
glowContainer.FadeOut(hover_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flash()
|
|
||||||
{
|
|
||||||
var flash = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both
|
|
||||||
};
|
|
||||||
|
|
||||||
colourContainer.Add(flash);
|
|
||||||
|
|
||||||
flash.Colour = ButtonColour;
|
|
||||||
flash.Blending = BlendingParameters.Additive;
|
|
||||||
flash.Alpha = 0.3f;
|
|
||||||
flash.FadeOutFromOne(click_duration);
|
|
||||||
flash.Expire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateGlow()
|
private void updateGlow()
|
||||||
{
|
{
|
||||||
leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour);
|
leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour);
|
||||||
|
@ -6,12 +6,10 @@ using System;
|
|||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class OsuEnumDropdown<T> : OsuDropdown<T>
|
public class OsuEnumDropdown<T> : OsuDropdown<T>
|
||||||
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
public OsuEnumDropdown()
|
public OsuEnumDropdown()
|
||||||
{
|
{
|
||||||
if (!typeof(T).IsEnum)
|
|
||||||
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
|
|
||||||
|
|
||||||
Items = (T[])Enum.GetValues(typeof(T));
|
Items = (T[])Enum.GetValues(typeof(T));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
PP = PP,
|
PP = PP,
|
||||||
Beatmap = Beatmap,
|
Beatmap = Beatmap,
|
||||||
RulesetID = OnlineRulesetID,
|
RulesetID = OnlineRulesetID,
|
||||||
Hash = "online", // todo: temporary?
|
Hash = Replay ? "online" : string.Empty, // todo: temporary?
|
||||||
Rank = Rank,
|
Rank = Rank,
|
||||||
Ruleset = ruleset,
|
Ruleset = ruleset,
|
||||||
Mods = mods,
|
Mods = mods,
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Online
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
|
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
|
||||||
|
|
||||||
protected readonly Bindable<double> Progress = new Bindable<double>();
|
protected readonly BindableNumber<double> Progress = new BindableNumber<double> { MinValue = 0, MaxValue = 1 };
|
||||||
|
|
||||||
protected DownloadTrackingComposite(TModel model = null)
|
protected DownloadTrackingComposite(TModel model = null)
|
||||||
{
|
{
|
||||||
|
@ -45,23 +45,25 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
[JsonProperty("beatmap")]
|
[JsonProperty("beatmap")]
|
||||||
private APIBeatmap apiBeatmap { get; set; }
|
private APIBeatmap apiBeatmap { get; set; }
|
||||||
|
|
||||||
|
private APIMod[] allowedModsBacking;
|
||||||
|
|
||||||
[JsonProperty("allowed_mods")]
|
[JsonProperty("allowed_mods")]
|
||||||
private APIMod[] allowedMods
|
private APIMod[] allowedMods
|
||||||
{
|
{
|
||||||
get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
|
get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
|
||||||
set => _allowedMods = value;
|
set => allowedModsBacking = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private APIMod[] requiredModsBacking;
|
||||||
|
|
||||||
[JsonProperty("required_mods")]
|
[JsonProperty("required_mods")]
|
||||||
private APIMod[] requiredMods
|
private APIMod[] requiredMods
|
||||||
{
|
{
|
||||||
get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
|
get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
|
||||||
set => _requiredMods = value;
|
set => requiredModsBacking = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapInfo beatmap;
|
private BeatmapInfo beatmap;
|
||||||
private APIMod[] _allowedMods;
|
|
||||||
private APIMod[] _requiredMods;
|
|
||||||
|
|
||||||
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
|
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
|
||||||
{
|
{
|
||||||
@ -70,20 +72,20 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
|
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
|
||||||
Ruleset = rulesets.GetRuleset(RulesetID);
|
Ruleset = rulesets.GetRuleset(RulesetID);
|
||||||
|
|
||||||
if (_allowedMods != null)
|
if (allowedModsBacking != null)
|
||||||
{
|
{
|
||||||
AllowedMods.Clear();
|
AllowedMods.Clear();
|
||||||
AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym)));
|
AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym)));
|
||||||
|
|
||||||
_allowedMods = null;
|
allowedModsBacking = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_requiredMods != null)
|
if (requiredModsBacking != null)
|
||||||
{
|
{
|
||||||
RequiredMods.Clear();
|
RequiredMods.Clear();
|
||||||
RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym)));
|
RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym)));
|
||||||
|
|
||||||
_requiredMods = null;
|
requiredModsBacking = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
for (int i = 0; i < value.Count; i++)
|
for (int i = 0; i < value.Count; i++)
|
||||||
backgroundFlow.Add(new ScoreTableRowBackground(i));
|
backgroundFlow.Add(new ScoreTableRowBackground(i, value[i]));
|
||||||
|
|
||||||
Columns = createHeaders(value[0]);
|
Columns = createHeaders(value[0]);
|
||||||
Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular();
|
Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular();
|
||||||
|
@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.BeatmapSet.Scores
|
namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||||
{
|
{
|
||||||
@ -17,8 +19,14 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
private readonly Box hoveredBackground;
|
private readonly Box hoveredBackground;
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
|
|
||||||
public ScoreTableRowBackground(int index)
|
private readonly int index;
|
||||||
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
|
public ScoreTableRowBackground(int index, ScoreInfo score)
|
||||||
{
|
{
|
||||||
|
this.index = index;
|
||||||
|
this.score = score;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = 25;
|
Height = 25;
|
||||||
|
|
||||||
@ -37,16 +45,21 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (index % 2 != 0)
|
|
||||||
background.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours, IAPIProvider api)
|
||||||
{
|
{
|
||||||
hoveredBackground.Colour = colours.Gray4;
|
var isOwnScore = api.LocalUser.Value.Id == score.UserID;
|
||||||
background.Colour = colours.Gray3;
|
|
||||||
|
if (isOwnScore)
|
||||||
|
background.Colour = colours.GreenDarker;
|
||||||
|
else if (index % 2 == 0)
|
||||||
|
background.Colour = colours.Gray3;
|
||||||
|
else
|
||||||
|
background.Alpha = 0;
|
||||||
|
|
||||||
|
hoveredBackground.Colour = isOwnScore ? colours.GreenDark : colours.Gray4;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -167,10 +167,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
|
||||||
SelectNext(1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
SelectNext(-1);
|
SelectNext(-1);
|
||||||
break;
|
break;
|
||||||
@ -180,6 +176,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
SelectNext(1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Select the next available mod in a specified direction.
|
/// Select the next available mod in a specified direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
56
osu.Game/Overlays/Mods/ModControlSection.cs
Normal file
56
osu.Game/Overlays/Mods/ModControlSection.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// 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.Containers;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public class ModControlSection : Container
|
||||||
|
{
|
||||||
|
protected FillFlowContainer FlowContent;
|
||||||
|
protected override Container<Drawable> Content => FlowContent;
|
||||||
|
|
||||||
|
public readonly Mod Mod;
|
||||||
|
|
||||||
|
public ModControlSection(Mod mod)
|
||||||
|
{
|
||||||
|
Mod = mod;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
FlowContent = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 30 },
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
};
|
||||||
|
|
||||||
|
AddRange(Mod.CreateSettingsControls());
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AddRangeInternal(new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = Mod.Name,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
|
Colour = colours.Yellow,
|
||||||
|
},
|
||||||
|
FlowContent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
modsLoadCts?.Cancel();
|
modsLoadCts?.Cancel();
|
||||||
|
|
||||||
|
if (modContainers.Length == 0)
|
||||||
|
{
|
||||||
|
ModIconsLoaded = true;
|
||||||
|
headerLabel.Hide();
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ModIconsLoaded = false;
|
ModIconsLoaded = false;
|
||||||
|
|
||||||
LoadComponentsAsync(modContainers, c =>
|
LoadComponentsAsync(modContainers, c =>
|
||||||
@ -67,17 +76,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
buttons = modContainers.OfType<ModButton>().ToArray();
|
buttons = modContainers.OfType<ModButton>().ToArray();
|
||||||
|
|
||||||
if (value.Any())
|
headerLabel.FadeIn(200);
|
||||||
{
|
this.FadeIn(200);
|
||||||
headerLabel.FadeIn(200);
|
|
||||||
this.FadeIn(200);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// transition here looks weird as mods instantly disappear.
|
|
||||||
headerLabel.Hide();
|
|
||||||
Hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -31,6 +32,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
public class ModSelectOverlay : WaveOverlayContainer
|
public class ModSelectOverlay : WaveOverlayContainer
|
||||||
{
|
{
|
||||||
protected readonly TriangleButton DeselectAllButton;
|
protected readonly TriangleButton DeselectAllButton;
|
||||||
|
protected readonly TriangleButton CustomiseButton;
|
||||||
protected readonly TriangleButton CloseButton;
|
protected readonly TriangleButton CloseButton;
|
||||||
|
|
||||||
protected readonly OsuSpriteText MultiplierLabel;
|
protected readonly OsuSpriteText MultiplierLabel;
|
||||||
@ -42,6 +44,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
protected readonly FillFlowContainer<ModSection> ModSectionsContainer;
|
protected readonly FillFlowContainer<ModSection> ModSectionsContainer;
|
||||||
|
|
||||||
|
protected readonly FillFlowContainer<ModControlSection> ModSettingsContent;
|
||||||
|
|
||||||
|
protected readonly Container ModSettingsContainer;
|
||||||
|
|
||||||
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||||
|
|
||||||
protected readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
protected readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
|
||||||
@ -226,6 +232,17 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Right = 20
|
Right = 20
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CustomiseButton = new TriangleButton
|
||||||
|
{
|
||||||
|
Width = 180,
|
||||||
|
Text = "Customisation",
|
||||||
|
Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1,
|
||||||
|
Enabled = { Value = false },
|
||||||
|
Margin = new MarginPadding
|
||||||
|
{
|
||||||
|
Right = 20
|
||||||
|
}
|
||||||
|
},
|
||||||
CloseButton = new TriangleButton
|
CloseButton = new TriangleButton
|
||||||
{
|
{
|
||||||
Width = 180,
|
Width = 180,
|
||||||
@ -271,6 +288,36 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ModSettingsContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Width = 0.25f,
|
||||||
|
Alpha = 0,
|
||||||
|
X = -100,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = new Color4(0, 0, 0, 192)
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = ModSettingsContent = new FillFlowContainer<ModControlSection>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0f, 10f),
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,12 +428,14 @@ namespace osu.Game.Overlays.Mods
|
|||||||
refreshSelectedMods();
|
refreshSelectedMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> e)
|
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||||
{
|
{
|
||||||
foreach (var section in ModSectionsContainer.Children)
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList());
|
section.SelectTypes(mods.NewValue.Select(m => m.GetType()).ToList());
|
||||||
|
|
||||||
updateMods();
|
updateMods();
|
||||||
|
|
||||||
|
updateModSettings(mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMods()
|
private void updateMods()
|
||||||
@ -411,6 +460,25 @@ namespace osu.Game.Overlays.Mods
|
|||||||
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
|
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateModSettings(ValueChangedEvent<IReadOnlyList<Mod>> selectedMods)
|
||||||
|
{
|
||||||
|
foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue))
|
||||||
|
{
|
||||||
|
var controls = added.CreateSettingsControls().ToList();
|
||||||
|
if (controls.Count > 0)
|
||||||
|
ModSettingsContent.Add(new ModControlSection(added) { Children = controls });
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue))
|
||||||
|
ModSettingsContent.RemoveAll(section => section.Mod == removed);
|
||||||
|
|
||||||
|
bool hasSettings = ModSettingsContent.Children.Count > 0;
|
||||||
|
CustomiseButton.Enabled.Value = hasSettings;
|
||||||
|
|
||||||
|
if (!hasSettings)
|
||||||
|
ModSettingsContainer.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
private void modButtonPressed(Mod selectedMod)
|
private void modButtonPressed(Mod selectedMod)
|
||||||
{
|
{
|
||||||
if (selectedMod != null)
|
if (selectedMod != null)
|
||||||
|
@ -261,8 +261,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
if (allowRateAdjustments)
|
if (allowRateAdjustments)
|
||||||
{
|
{
|
||||||
foreach (var mod in mods.Value.OfType<IApplicableToClock>())
|
foreach (var mod in mods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToClock(track);
|
mod.ApplyToTrack(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,6 +12,7 @@ using osu.Game.Graphics.Containers;
|
|||||||
namespace osu.Game.Overlays.SearchableList
|
namespace osu.Game.Overlays.SearchableList
|
||||||
{
|
{
|
||||||
public class DisplayStyleControl<T> : Container
|
public class DisplayStyleControl<T> : Container
|
||||||
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
public readonly SlimEnumDropdown<T> Dropdown;
|
public readonly SlimEnumDropdown<T> Dropdown;
|
||||||
public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
|
public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();
|
||||||
|
@ -13,7 +13,9 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.SearchableList
|
namespace osu.Game.Overlays.SearchableList
|
||||||
{
|
{
|
||||||
public abstract class SearchableListFilterControl<T, U> : Container
|
public abstract class SearchableListFilterControl<TTab, TCategory> : Container
|
||||||
|
where TTab : struct, Enum
|
||||||
|
where TCategory : struct, Enum
|
||||||
{
|
{
|
||||||
private const float padding = 10;
|
private const float padding = 10;
|
||||||
|
|
||||||
@ -21,12 +23,12 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
private readonly Box tabStrip;
|
private readonly Box tabStrip;
|
||||||
|
|
||||||
public readonly SearchTextBox Search;
|
public readonly SearchTextBox Search;
|
||||||
public readonly PageTabControl<T> Tabs;
|
public readonly PageTabControl<TTab> Tabs;
|
||||||
public readonly DisplayStyleControl<U> DisplayStyleControl;
|
public readonly DisplayStyleControl<TCategory> DisplayStyleControl;
|
||||||
|
|
||||||
protected abstract Color4 BackgroundColour { get; }
|
protected abstract Color4 BackgroundColour { get; }
|
||||||
protected abstract T DefaultTab { get; }
|
protected abstract TTab DefaultTab { get; }
|
||||||
protected abstract U DefaultCategory { get; }
|
protected abstract TCategory DefaultCategory { get; }
|
||||||
protected virtual Drawable CreateSupplementaryControls() => null;
|
protected virtual Drawable CreateSupplementaryControls() => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,9 +38,6 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
|
|
||||||
protected SearchableListFilterControl()
|
protected SearchableListFilterControl()
|
||||||
{
|
{
|
||||||
if (!typeof(T).IsEnum)
|
|
||||||
throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument");
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
var controls = CreateSupplementaryControls();
|
var controls = CreateSupplementaryControls();
|
||||||
@ -90,7 +89,7 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Padding = new MarginPadding { Right = 225 },
|
Padding = new MarginPadding { Right = 225 },
|
||||||
Child = Tabs = new PageTabControl<T>
|
Child = Tabs = new PageTabControl<TTab>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
},
|
},
|
||||||
@ -105,7 +104,7 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DisplayStyleControl = new DisplayStyleControl<U>
|
DisplayStyleControl = new DisplayStyleControl<TCategory>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
namespace osu.Game.Overlays.SearchableList
|
namespace osu.Game.Overlays.SearchableList
|
||||||
{
|
{
|
||||||
public abstract class SearchableListHeader<T> : Container
|
public abstract class SearchableListHeader<T> : Container
|
||||||
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
public readonly HeaderTabControl<T> Tabs;
|
public readonly HeaderTabControl<T> Tabs;
|
||||||
|
|
||||||
@ -24,9 +25,6 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
|
|
||||||
protected SearchableListHeader()
|
protected SearchableListHeader()
|
||||||
{
|
{
|
||||||
if (!typeof(T).IsEnum)
|
|
||||||
throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument");
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = 90;
|
Height = 90;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -16,19 +17,22 @@ namespace osu.Game.Overlays.SearchableList
|
|||||||
public const float WIDTH_PADDING = 80;
|
public const float WIDTH_PADDING = 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class SearchableListOverlay<T, U, S> : SearchableListOverlay
|
public abstract class SearchableListOverlay<THeader, TTab, TCategory> : SearchableListOverlay
|
||||||
|
where THeader : struct, Enum
|
||||||
|
where TTab : struct, Enum
|
||||||
|
where TCategory : struct, Enum
|
||||||
{
|
{
|
||||||
private readonly Container scrollContainer;
|
private readonly Container scrollContainer;
|
||||||
|
|
||||||
protected readonly SearchableListHeader<T> Header;
|
protected readonly SearchableListHeader<THeader> Header;
|
||||||
protected readonly SearchableListFilterControl<U, S> Filter;
|
protected readonly SearchableListFilterControl<TTab, TCategory> Filter;
|
||||||
protected readonly FillFlowContainer ScrollFlow;
|
protected readonly FillFlowContainer ScrollFlow;
|
||||||
|
|
||||||
protected abstract Color4 BackgroundColour { get; }
|
protected abstract Color4 BackgroundColour { get; }
|
||||||
protected abstract Color4 TrianglesColourLight { get; }
|
protected abstract Color4 TrianglesColourLight { get; }
|
||||||
protected abstract Color4 TrianglesColourDark { get; }
|
protected abstract Color4 TrianglesColourDark { get; }
|
||||||
protected abstract SearchableListHeader<T> CreateHeader();
|
protected abstract SearchableListHeader<THeader> CreateHeader();
|
||||||
protected abstract SearchableListFilterControl<U, S> CreateFilterControl();
|
protected abstract SearchableListFilterControl<TTab, TCategory> CreateFilterControl();
|
||||||
|
|
||||||
protected SearchableListOverlay()
|
protected SearchableListOverlay()
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,6 +12,7 @@ using osuTK;
|
|||||||
namespace osu.Game.Overlays.SearchableList
|
namespace osu.Game.Overlays.SearchableList
|
||||||
{
|
{
|
||||||
public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
|
public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
|
||||||
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
|
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
public class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
||||||
|
where T : struct, Enum
|
||||||
{
|
{
|
||||||
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
||||||
|
|
||||||
|
@ -53,27 +53,10 @@ namespace osu.Game.Overlays.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hold a reference to the provided bindable so we don't have to in every settings section.
|
|
||||||
private Bindable<T> bindable;
|
|
||||||
|
|
||||||
public virtual Bindable<T> Bindable
|
public virtual Bindable<T> Bindable
|
||||||
{
|
{
|
||||||
get => bindable;
|
get => controlWithCurrent.Current;
|
||||||
|
set => controlWithCurrent.Current = value;
|
||||||
set
|
|
||||||
{
|
|
||||||
if (bindable != null)
|
|
||||||
controlWithCurrent?.Current.UnbindFrom(bindable);
|
|
||||||
|
|
||||||
bindable = value;
|
|
||||||
controlWithCurrent?.Current.BindTo(bindable);
|
|
||||||
|
|
||||||
if (ShowsDefaultIndicator)
|
|
||||||
{
|
|
||||||
restoreDefaultButton.Bindable = bindable.GetBoundCopy();
|
|
||||||
restoreDefaultButton.Bindable.TriggerChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText } : new List<string>(Keywords) { LabelText }.ToArray();
|
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText } : new List<string>(Keywords) { LabelText }.ToArray();
|
||||||
@ -110,7 +93,12 @@ namespace osu.Game.Overlays.Settings
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
if (controlWithCurrent != null)
|
if (controlWithCurrent != null)
|
||||||
|
{
|
||||||
controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
|
controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
|
||||||
|
|
||||||
|
if (ShowsDefaultIndicator)
|
||||||
|
restoreDefaultButton.Bindable = controlWithCurrent.Current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RestoreDefaultValueButton : Container, IHasTooltip
|
private class RestoreDefaultValueButton : Container, IHasTooltip
|
||||||
@ -125,6 +113,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
bindable = value;
|
bindable = value;
|
||||||
bindable.ValueChanged += _ => UpdateState();
|
bindable.ValueChanged += _ => UpdateState();
|
||||||
bindable.DisabledChanged += _ => UpdateState();
|
bindable.DisabledChanged += _ => UpdateState();
|
||||||
|
UpdateState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,16 +3,14 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Screens.Ranking;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
{
|
{
|
||||||
@ -36,21 +34,21 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
protected override bool DimMainContent => false; // dimming is handled by main overlay
|
protected override bool DimMainContent => false; // dimming is handled by main overlay
|
||||||
|
|
||||||
private class BackButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
|
private class BackButton : OsuButton
|
||||||
{
|
{
|
||||||
private AspectContainer aspect;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Size = new Vector2(Sidebar.DEFAULT_WIDTH);
|
Size = new Vector2(Sidebar.DEFAULT_WIDTH);
|
||||||
Children = new Drawable[]
|
|
||||||
|
BackgroundColour = Color4.Black;
|
||||||
|
|
||||||
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
aspect = new AspectContainer
|
new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SpriteIcon
|
new SpriteIcon
|
||||||
@ -71,34 +69,8 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
|
||||||
{
|
|
||||||
aspect.ScaleTo(0.75f, 2000, Easing.OutQuint);
|
|
||||||
return base.OnMouseDown(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnMouseUp(MouseUpEvent e)
|
|
||||||
{
|
|
||||||
aspect.ScaleTo(1, 1000, Easing.OutElastic);
|
|
||||||
return base.OnMouseUp(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
|
||||||
{
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case GlobalAction.Back:
|
|
||||||
Click();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool OnReleased(GlobalAction action) => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Configuration
|
namespace osu.Game.Rulesets.Configuration
|
||||||
{
|
{
|
||||||
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
|
public abstract class RulesetConfigManager<TLookup> : DatabasedConfigManager<TLookup>, IRulesetConfigManager
|
||||||
where T : struct
|
where TLookup : struct, Enum
|
||||||
{
|
{
|
||||||
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
||||||
: base(settings, ruleset, variant)
|
: base(settings, ruleset, variant)
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
|
|
||||||
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
|
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
|
||||||
|
|
||||||
var clock = new StopwatchClock();
|
var track = new TrackVirtual(10000);
|
||||||
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
|
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||||
|
|
||||||
return calculate(playableBeatmap, mods, clock.Rate);
|
return calculate(playableBeatmap, mods, track.Rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
|
|
||||||
protected virtual void ApplyMods(Mod[] mods)
|
protected virtual void ApplyMods(Mod[] mods)
|
||||||
{
|
{
|
||||||
var clock = new StopwatchClock();
|
var track = new TrackVirtual(10000);
|
||||||
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
|
mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||||
TimeRate = clock.Rate;
|
TimeRate = track.Rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
|
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
|
||||||
|
@ -34,7 +34,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
where TObject : HitObject
|
where TObject : HitObject
|
||||||
{
|
{
|
||||||
protected IRulesetConfigManager Config { get; private set; }
|
protected IRulesetConfigManager Config { get; private set; }
|
||||||
protected EditorBeatmap<TObject> EditorBeatmap { get; private set; }
|
|
||||||
|
protected new EditorBeatmap<TObject> EditorBeatmap { get; private set; }
|
||||||
|
|
||||||
protected readonly Ruleset Ruleset;
|
protected readonly Ruleset Ruleset;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
|
beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap);
|
||||||
|
|
||||||
EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
|
base.EditorBeatmap = EditorBeatmap = new EditorBeatmap<TObject>(playableBeatmap);
|
||||||
EditorBeatmap.HitObjectAdded += addHitObject;
|
EditorBeatmap.HitObjectAdded += addHitObject;
|
||||||
EditorBeatmap.HitObjectRemoved += removeHitObject;
|
EditorBeatmap.HitObjectRemoved += removeHitObject;
|
||||||
EditorBeatmap.StartTimeChanged += UpdateHitObject;
|
EditorBeatmap.StartTimeChanged += UpdateHitObject;
|
||||||
@ -333,6 +335,11 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract IEnumerable<DrawableHitObject> HitObjects { get; }
|
public abstract IEnumerable<DrawableHitObject> HitObjects { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An editor-specific beatmap, exposing mutation events.
|
||||||
|
/// </summary>
|
||||||
|
public IEditorBeatmap EditorBeatmap { get; protected set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
|
/// Whether the user's cursor is currently in an area of the <see cref="HitObjectComposer"/> that is valid for placement.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Audio.Track;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An interface for mods that make adjustments to the track.
|
/// An interface for mods that make adjustments to the track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IApplicableToClock : IApplicableMod
|
public interface IApplicableToTrack : IApplicableMod
|
||||||
{
|
{
|
||||||
void ApplyToClock(IAdjustableClock clock);
|
void ApplyToTrack(Track track);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType());
|
public virtual Mod CreateCopy() => (Mod)MemberwiseClone();
|
||||||
|
|
||||||
public bool Equals(IMod other) => GetType() == other?.GetType();
|
public bool Equals(IMod other) => GetType() == other?.GetType();
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Timing;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
@ -14,12 +13,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override IconUsage Icon => FontAwesome.Solid.Question;
|
public override IconUsage Icon => FontAwesome.Solid.Question;
|
||||||
public override string Description => "Whoaaaaa...";
|
public override string Description => "Whoaaaaa...";
|
||||||
|
|
||||||
public override void ApplyToClock(IAdjustableClock clock)
|
public override void ApplyToTrack(Track track)
|
||||||
{
|
{
|
||||||
if (clock is IHasPitchAdjust pitchAdjust)
|
track.Frequency.Value *= RateAdjust;
|
||||||
pitchAdjust.PitchAdjust *= RateAdjust;
|
|
||||||
else
|
|
||||||
base.ApplyToClock(clock);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock
|
public abstract class ModDoubleTime : ModTimeAdjust
|
||||||
{
|
{
|
||||||
public override string Name => "Double Time";
|
public override string Name => "Double Time";
|
||||||
public override string Acronym => "DT";
|
public override string Acronym => "DT";
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock
|
public abstract class ModHalfTime : ModTimeAdjust
|
||||||
{
|
{
|
||||||
public override string Name => "Half Time";
|
public override string Name => "Half Time";
|
||||||
public override string Acronym => "HT";
|
public override string Acronym => "HT";
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
@ -15,12 +14,9 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
public override IconUsage Icon => OsuIcon.ModNightcore;
|
public override IconUsage Icon => OsuIcon.ModNightcore;
|
||||||
public override string Description => "Uguuuuuuuu...";
|
public override string Description => "Uguuuuuuuu...";
|
||||||
|
|
||||||
public override void ApplyToClock(IAdjustableClock clock)
|
public override void ApplyToTrack(Track track)
|
||||||
{
|
{
|
||||||
if (clock is IHasPitchAdjust pitchAdjust)
|
track.Frequency.Value *= RateAdjust;
|
||||||
pitchAdjust.PitchAdjust *= RateAdjust;
|
|
||||||
else
|
|
||||||
base.ApplyToClock(clock);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,23 +2,19 @@
|
|||||||
// 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 osu.Framework.Audio;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Timing;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModTimeAdjust : Mod
|
public abstract class ModTimeAdjust : Mod, IApplicableToTrack
|
||||||
{
|
{
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
|
||||||
|
|
||||||
protected abstract double RateAdjust { get; }
|
protected abstract double RateAdjust { get; }
|
||||||
|
|
||||||
public virtual void ApplyToClock(IAdjustableClock clock)
|
public virtual void ApplyToTrack(Track track)
|
||||||
{
|
{
|
||||||
if (clock is IHasTempoAdjust tempo)
|
track.Tempo.Value *= RateAdjust;
|
||||||
tempo.TempoAdjust *= RateAdjust;
|
|
||||||
else
|
|
||||||
clock.Rate *= RateAdjust;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap
|
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The point in the beatmap at which the final ramping rate should be reached.
|
/// The point in the beatmap at which the final ramping rate should be reached.
|
||||||
@ -24,11 +23,11 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
private double finalRateTime;
|
private double finalRateTime;
|
||||||
private double beginRampTime;
|
private double beginRampTime;
|
||||||
private IAdjustableClock clock;
|
private Track track;
|
||||||
|
|
||||||
public virtual void ApplyToClock(IAdjustableClock clock)
|
public virtual void ApplyToTrack(Track track)
|
||||||
{
|
{
|
||||||
this.clock = clock;
|
this.track = track;
|
||||||
|
|
||||||
lastAdjust = 1;
|
lastAdjust = 1;
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
public virtual void Update(Playfield playfield)
|
public virtual void Update(Playfield playfield)
|
||||||
{
|
{
|
||||||
applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime);
|
applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double lastAdjust = 1;
|
private double lastAdjust = 1;
|
||||||
@ -59,23 +58,8 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment));
|
double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment));
|
||||||
|
|
||||||
switch (clock)
|
track.Tempo.Value /= lastAdjust;
|
||||||
{
|
track.Tempo.Value *= adjust;
|
||||||
case IHasPitchAdjust pitch:
|
|
||||||
pitch.PitchAdjust /= lastAdjust;
|
|
||||||
pitch.PitchAdjust *= adjust;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IHasTempoAdjust tempo:
|
|
||||||
tempo.TempoAdjust /= lastAdjust;
|
|
||||||
tempo.TempoAdjust *= adjust;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
clock.Rate /= lastAdjust;
|
|
||||||
clock.Rate *= adjust;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastAdjust = adjust;
|
lastAdjust = adjust;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
||||||
@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||||
List<IList<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
newCombo |= forceNewCombo;
|
newCombo |= forceNewCombo;
|
||||||
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
|
|||||||
X = position.X,
|
X = position.X,
|
||||||
NewCombo = FirstObject || newCombo,
|
NewCombo = FirstObject || newCombo,
|
||||||
ComboOffset = comboOffset,
|
ComboOffset = comboOffset,
|
||||||
Path = new SliderPath(pathType, controlPoints, length),
|
Path = new SliderPath(controlPoints, length),
|
||||||
NodeSamples = nodeSamples,
|
NodeSamples = nodeSamples,
|
||||||
RepeatCount = repeatCount
|
RepeatCount = repeatCount
|
||||||
};
|
};
|
||||||
|
@ -115,12 +115,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
|
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// osu-stable special-cased colinear perfect curves to a CurveType.Linear
|
|
||||||
static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
|
|
||||||
|
|
||||||
if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
|
|
||||||
pathType = PathType.Linear;
|
|
||||||
|
|
||||||
int repeatCount = Parsing.ParseInt(split[6]);
|
int repeatCount = Parsing.ParseInt(split[6]);
|
||||||
|
|
||||||
if (repeatCount > 9000)
|
if (repeatCount > 9000)
|
||||||
@ -187,7 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
for (int i = 0; i < nodes; i++)
|
for (int i = 0; i < nodes; i++)
|
||||||
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
|
||||||
|
|
||||||
result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);
|
result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples);
|
||||||
|
|
||||||
// The samples are played when the slider ends, which is the last node
|
// The samples are played when the slider ends, which is the last node
|
||||||
result.Samples = nodeSamples[nodeSamples.Count - 1];
|
result.Samples = nodeSamples[nodeSamples.Count - 1];
|
||||||
@ -259,6 +253,44 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
bankInfo.Filename = split.Length > 4 ? split[4] : null;
|
bankInfo.Filename = split.Length > 4 ? split[4] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type)
|
||||||
|
{
|
||||||
|
if (type == PathType.PerfectCurve)
|
||||||
|
{
|
||||||
|
if (vertices.Length != 3)
|
||||||
|
type = PathType.Bezier;
|
||||||
|
else if (isLinear(vertices))
|
||||||
|
{
|
||||||
|
// osu-stable special-cased colinear perfect curves to a linear path
|
||||||
|
type = PathType.Linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var points = new List<PathControlPoint>(vertices.Length)
|
||||||
|
{
|
||||||
|
new PathControlPoint
|
||||||
|
{
|
||||||
|
Position = { Value = vertices[0] },
|
||||||
|
Type = { Value = type }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 1; i < vertices.Length; i++)
|
||||||
|
{
|
||||||
|
if (vertices[i] == vertices[i - 1])
|
||||||
|
{
|
||||||
|
points[points.Count - 1].Type.Value = type;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
points.Add(new PathControlPoint { Position = { Value = vertices[i] } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return points.ToArray();
|
||||||
|
|
||||||
|
static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a legacy Hit-type hit object.
|
/// Creates a legacy Hit-type hit object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -276,11 +308,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
|||||||
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
|
||||||
/// <param name="controlPoints">The slider control points.</param>
|
/// <param name="controlPoints">The slider control points.</param>
|
||||||
/// <param name="length">The slider length.</param>
|
/// <param name="length">The slider length.</param>
|
||||||
/// <param name="pathType">The slider curve type.</param>
|
|
||||||
/// <param name="repeatCount">The slider repeat count.</param>
|
/// <param name="repeatCount">The slider repeat count.</param>
|
||||||
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
|
||||||
/// <returns>The hit object.</returns>
|
/// <returns>The hit object.</returns>
|
||||||
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||||
List<IList<HitSampleInfo>> nodeSamples);
|
List<IList<HitSampleInfo>> nodeSamples);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
||||||
@ -26,13 +25,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||||
List<IList<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
return new ConvertSlider
|
return new ConvertSlider
|
||||||
{
|
{
|
||||||
X = position.X,
|
X = position.X,
|
||||||
Path = new SliderPath(pathType, controlPoints, length),
|
Path = new SliderPath(controlPoints, length),
|
||||||
NodeSamples = nodeSamples,
|
NodeSamples = nodeSamples,
|
||||||
RepeatCount = repeatCount
|
RepeatCount = repeatCount
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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 osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||||
List<IList<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
newCombo |= forceNewCombo;
|
newCombo |= forceNewCombo;
|
||||||
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
|
|||||||
Position = position,
|
Position = position,
|
||||||
NewCombo = FirstObject || newCombo,
|
NewCombo = FirstObject || newCombo,
|
||||||
ComboOffset = comboOffset,
|
ComboOffset = comboOffset,
|
||||||
Path = new SliderPath(pathType, controlPoints, length),
|
Path = new SliderPath(controlPoints, length),
|
||||||
NodeSamples = nodeSamples,
|
NodeSamples = nodeSamples,
|
||||||
RepeatCount = repeatCount
|
RepeatCount = repeatCount
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// 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 osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -23,12 +22,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
|
|||||||
return new ConvertHit();
|
return new ConvertHit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
|
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
|
||||||
List<IList<HitSampleInfo>> nodeSamples)
|
List<IList<HitSampleInfo>> nodeSamples)
|
||||||
{
|
{
|
||||||
return new ConvertSlider
|
return new ConvertSlider
|
||||||
{
|
{
|
||||||
Path = new SliderPath(pathType, controlPoints, length),
|
Path = new SliderPath(controlPoints, length),
|
||||||
NodeSamples = nodeSamples,
|
NodeSamples = nodeSamples,
|
||||||
RepeatCount = repeatCount
|
RepeatCount = repeatCount
|
||||||
};
|
};
|
||||||
|
52
osu.Game/Rulesets/Objects/PathControlPoint.cs
Normal file
52
osu.Game/Rulesets/Objects/PathControlPoint.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Objects
|
||||||
|
{
|
||||||
|
public class PathControlPoint : IEquatable<PathControlPoint>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The position of this <see cref="PathControlPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<Vector2> Position = new Bindable<Vector2>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of path segment starting at this <see cref="PathControlPoint"/>.
|
||||||
|
/// If null, this <see cref="PathControlPoint"/> will be a part of the previous path segment.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<PathType?> Type = new Bindable<PathType?>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when any property of this <see cref="PathControlPoint"/> is changed.
|
||||||
|
/// </summary>
|
||||||
|
internal event Action Changed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PathControlPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
public PathControlPoint()
|
||||||
|
{
|
||||||
|
Position.ValueChanged += _ => Changed?.Invoke();
|
||||||
|
Type.ValueChanged += _ => Changed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="PathControlPoint"/> with a provided position and type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The initial position.</param>
|
||||||
|
/// <param name="type">The initial type.</param>
|
||||||
|
public PathControlPoint(Vector2 position, PathType? type = null)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
Position.Value = position;
|
||||||
|
Type.Value = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,68 +1,86 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects
|
namespace osu.Game.Rulesets.Objects
|
||||||
{
|
{
|
||||||
public struct SliderPath : IEquatable<SliderPath>
|
public class SliderPath
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current version of this <see cref="SliderPath"/>. Updated when any change to the path occurs.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public IBindable<int> Version => version;
|
||||||
|
|
||||||
|
private readonly Bindable<int> version = new Bindable<int>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The user-set distance of the path. If non-null, <see cref="Distance"/> will match this value,
|
/// The user-set distance of the path. If non-null, <see cref="Distance"/> will match this value,
|
||||||
/// and the path will be shortened/lengthened to match this length.
|
/// and the path will be shortened/lengthened to match this length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly double? ExpectedDistance;
|
public readonly Bindable<double?> ExpectedDistance = new Bindable<double?>();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The type of path.
|
|
||||||
/// </summary>
|
|
||||||
public readonly PathType Type;
|
|
||||||
|
|
||||||
[JsonProperty]
|
|
||||||
private Vector2[] controlPoints;
|
|
||||||
|
|
||||||
private List<Vector2> calculatedPath;
|
|
||||||
private List<double> cumulativeLength;
|
|
||||||
|
|
||||||
private bool isInitialised;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="SliderPath"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">The type of path.</param>
|
|
||||||
/// <param name="controlPoints">The control points of the path.</param>
|
|
||||||
/// <param name="expectedDistance">A user-set distance of the path that may be shorter or longer than the true distance between all
|
|
||||||
/// <paramref name="controlPoints"/>. The path will be shortened/lengthened to match this length.
|
|
||||||
/// If null, the path will use the true distance between all <paramref name="controlPoints"/>.</param>
|
|
||||||
[JsonConstructor]
|
|
||||||
public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null)
|
|
||||||
{
|
|
||||||
this = default;
|
|
||||||
this.controlPoints = controlPoints;
|
|
||||||
|
|
||||||
Type = type;
|
|
||||||
ExpectedDistance = expectedDistance;
|
|
||||||
|
|
||||||
ensureInitialised();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The control points of the path.
|
/// The control points of the path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
public readonly BindableList<PathControlPoint> ControlPoints = new BindableList<PathControlPoint>();
|
||||||
public ReadOnlySpan<Vector2> ControlPoints
|
|
||||||
|
private readonly List<Vector2> calculatedPath = new List<Vector2>();
|
||||||
|
private readonly List<double> cumulativeLength = new List<double>();
|
||||||
|
private readonly Cached pathCache = new Cached();
|
||||||
|
|
||||||
|
private double calculatedLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SliderPath"/>.
|
||||||
|
/// </summary>
|
||||||
|
public SliderPath()
|
||||||
{
|
{
|
||||||
get
|
ExpectedDistance.ValueChanged += _ => invalidate();
|
||||||
|
|
||||||
|
ControlPoints.ItemsAdded += items =>
|
||||||
{
|
{
|
||||||
ensureInitialised();
|
foreach (var c in items)
|
||||||
return controlPoints.AsSpan();
|
c.Changed += invalidate;
|
||||||
}
|
|
||||||
|
invalidate();
|
||||||
|
};
|
||||||
|
|
||||||
|
ControlPoints.ItemsRemoved += items =>
|
||||||
|
{
|
||||||
|
foreach (var c in items)
|
||||||
|
c.Changed -= invalidate;
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="SliderPath"/> initialised with a list of control points.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="controlPoints">An optional set of <see cref="PathControlPoint"/>s to initialise the path with.</param>
|
||||||
|
/// <param name="expectedDistance">A user-set distance of the path that may be shorter or longer than the true distance between all control points.
|
||||||
|
/// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points.</param>
|
||||||
|
[JsonConstructor]
|
||||||
|
public SliderPath(PathControlPoint[] controlPoints, double? expectedDistance = null)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
ControlPoints.AddRange(controlPoints);
|
||||||
|
ExpectedDistance.Value = expectedDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null)
|
||||||
|
: this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? (PathType?)type : null)).ToArray(), expectedDistance)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -73,11 +91,23 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
ensureInitialised();
|
ensureValid();
|
||||||
return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1];
|
return cumulativeLength.Count == 0 ? 0 : cumulativeLength[cumulativeLength.Count - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance of the path prior to lengthening/shortening to account for <see cref="ExpectedDistance"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double CalculatedDistance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ensureValid();
|
||||||
|
return calculatedLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes the slider path until a given progress that ranges from 0 (beginning of the slider)
|
/// Computes the slider path until a given progress that ranges from 0 (beginning of the slider)
|
||||||
/// to 1 (end of the slider) and stores the generated path in the given list.
|
/// to 1 (end of the slider) and stores the generated path in the given list.
|
||||||
@ -87,7 +117,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// <param name="p1">End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
/// <param name="p1">End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
||||||
public void GetPathToProgress(List<Vector2> path, double p0, double p1)
|
public void GetPathToProgress(List<Vector2> path, double p0, double p1)
|
||||||
{
|
{
|
||||||
ensureInitialised();
|
ensureValid();
|
||||||
|
|
||||||
double d0 = progressToDistance(p0);
|
double d0 = progressToDistance(p0);
|
||||||
double d1 = progressToDistance(p1);
|
double d1 = progressToDistance(p1);
|
||||||
@ -116,40 +146,73 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Vector2 PositionAt(double progress)
|
public Vector2 PositionAt(double progress)
|
||||||
{
|
{
|
||||||
ensureInitialised();
|
ensureValid();
|
||||||
|
|
||||||
double d = progressToDistance(progress);
|
double d = progressToDistance(progress);
|
||||||
return interpolateVertices(indexOfDistance(d), d);
|
return interpolateVertices(indexOfDistance(d), d);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureInitialised()
|
private void invalidate()
|
||||||
{
|
{
|
||||||
if (isInitialised)
|
pathCache.Invalidate();
|
||||||
return;
|
version.Value++;
|
||||||
|
|
||||||
isInitialised = true;
|
|
||||||
|
|
||||||
controlPoints ??= Array.Empty<Vector2>();
|
|
||||||
calculatedPath = new List<Vector2>();
|
|
||||||
cumulativeLength = new List<double>();
|
|
||||||
|
|
||||||
calculatePath();
|
|
||||||
calculateCumulativeLength();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Vector2> calculateSubpath(ReadOnlySpan<Vector2> subControlPoints)
|
private void ensureValid()
|
||||||
{
|
{
|
||||||
switch (Type)
|
if (pathCache.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
calculatePath();
|
||||||
|
calculateLength();
|
||||||
|
|
||||||
|
pathCache.Validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculatePath()
|
||||||
|
{
|
||||||
|
calculatedPath.Clear();
|
||||||
|
|
||||||
|
if (ControlPoints.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector2[] vertices = new Vector2[ControlPoints.Count];
|
||||||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
||||||
|
vertices[i] = ControlPoints[i].Position.Value;
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < ControlPoints.Count; i++)
|
||||||
|
{
|
||||||
|
if (ControlPoints[i].Type.Value == null && i < ControlPoints.Count - 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The current vertex ends the segment
|
||||||
|
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
|
||||||
|
var segmentType = ControlPoints[start].Type.Value ?? PathType.Linear;
|
||||||
|
|
||||||
|
foreach (Vector2 t in calculateSubPath(segmentVertices, segmentType))
|
||||||
|
{
|
||||||
|
if (calculatedPath.Count == 0 || calculatedPath.Last() != t)
|
||||||
|
calculatedPath.Add(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the new segment at the current vertex
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
{
|
{
|
||||||
case PathType.Linear:
|
case PathType.Linear:
|
||||||
return PathApproximator.ApproximateLinear(subControlPoints);
|
return PathApproximator.ApproximateLinear(subControlPoints);
|
||||||
|
|
||||||
case PathType.PerfectCurve:
|
case PathType.PerfectCurve:
|
||||||
//we can only use CircularArc iff we have exactly three control points and no dissection.
|
if (subControlPoints.Length != 3)
|
||||||
if (ControlPoints.Length != 3 || subControlPoints.Length != 3)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Here we have exactly 3 control points. Attempt to fit a circular arc.
|
|
||||||
List<Vector2> subpath = PathApproximator.ApproximateCircularArc(subControlPoints);
|
List<Vector2> subpath = PathApproximator.ApproximateCircularArc(subControlPoints);
|
||||||
|
|
||||||
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
||||||
@ -165,74 +228,49 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
return PathApproximator.ApproximateBezier(subControlPoints);
|
return PathApproximator.ApproximateBezier(subControlPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void calculatePath()
|
private void calculateLength()
|
||||||
{
|
{
|
||||||
calculatedPath.Clear();
|
calculatedLength = 0;
|
||||||
|
|
||||||
// Sliders may consist of various subpaths separated by two consecutive vertices
|
|
||||||
// with the same position. The following loop parses these subpaths and computes
|
|
||||||
// their shape independently, consecutively appending them to calculatedPath.
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int end = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < ControlPoints.Length; ++i)
|
|
||||||
{
|
|
||||||
end++;
|
|
||||||
|
|
||||||
if (i == ControlPoints.Length - 1 || ControlPoints[i] == ControlPoints[i + 1])
|
|
||||||
{
|
|
||||||
ReadOnlySpan<Vector2> cpSpan = ControlPoints.Slice(start, end - start);
|
|
||||||
|
|
||||||
foreach (Vector2 t in calculateSubpath(cpSpan))
|
|
||||||
{
|
|
||||||
if (calculatedPath.Count == 0 || calculatedPath.Last() != t)
|
|
||||||
calculatedPath.Add(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void calculateCumulativeLength()
|
|
||||||
{
|
|
||||||
double l = 0;
|
|
||||||
|
|
||||||
cumulativeLength.Clear();
|
cumulativeLength.Clear();
|
||||||
cumulativeLength.Add(l);
|
cumulativeLength.Add(0);
|
||||||
|
|
||||||
for (int i = 0; i < calculatedPath.Count - 1; ++i)
|
for (int i = 0; i < calculatedPath.Count - 1; i++)
|
||||||
{
|
{
|
||||||
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
|
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
|
||||||
double d = diff.Length;
|
calculatedLength += diff.Length;
|
||||||
|
cumulativeLength.Add(calculatedLength);
|
||||||
// Shorted slider paths that are too long compared to the expected distance
|
|
||||||
if (ExpectedDistance.HasValue && ExpectedDistance - l < d)
|
|
||||||
{
|
|
||||||
calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((ExpectedDistance - l) / d);
|
|
||||||
calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i);
|
|
||||||
|
|
||||||
l = ExpectedDistance.Value;
|
|
||||||
cumulativeLength.Add(l);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
l += d;
|
|
||||||
cumulativeLength.Add(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lengthen slider paths that are too short compared to the expected distance
|
if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance)
|
||||||
if (ExpectedDistance.HasValue && l < ExpectedDistance && calculatedPath.Count > 1)
|
|
||||||
{
|
{
|
||||||
Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2];
|
// The last length is always incorrect
|
||||||
double d = diff.Length;
|
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
||||||
|
|
||||||
if (d <= 0)
|
int pathEndIndex = calculatedPath.Count - 1;
|
||||||
|
|
||||||
|
if (calculatedLength > expectedDistance)
|
||||||
|
{
|
||||||
|
// The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments
|
||||||
|
while (cumulativeLength.Count > 0 && cumulativeLength[cumulativeLength.Count - 1] >= expectedDistance)
|
||||||
|
{
|
||||||
|
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
||||||
|
calculatedPath.RemoveAt(pathEndIndex--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathEndIndex <= 0)
|
||||||
|
{
|
||||||
|
// The expected distance is negative or zero
|
||||||
|
// TODO: Perhaps negative path lengths should be disallowed altogether
|
||||||
|
cumulativeLength.Add(0);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
calculatedPath[calculatedPath.Count - 1] += diff * (float)((ExpectedDistance - l) / d);
|
// The direction of the segment to shorten or lengthen
|
||||||
cumulativeLength[calculatedPath.Count - 1] = ExpectedDistance.Value;
|
Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized();
|
||||||
|
|
||||||
|
calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[cumulativeLength.Count - 1]);
|
||||||
|
cumulativeLength.Add(expectedDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +310,5 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
double w = (d - d0) / (d1 - d0);
|
double w = (d - d0) / (d1 - d0);
|
||||||
return p0 + (p1 - p0) * (float)w;
|
return p0 + (p1 - p0) * (float)w;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(SliderPath other) => ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance == other.ExpectedDistance && Type == other.Type;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -511,15 +511,19 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
|
||||||
|
|
||||||
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
|
public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||||
|
|
||||||
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException();
|
public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
|
||||||
|
|
||||||
public BindableDouble Volume => throw new NotImplementedException();
|
public BindableNumber<double> Volume => throw new NotImplementedException();
|
||||||
|
|
||||||
public BindableDouble Balance => throw new NotImplementedException();
|
public BindableNumber<double> Balance => throw new NotImplementedException();
|
||||||
|
|
||||||
public BindableDouble Frequency => throw new NotImplementedException();
|
public BindableNumber<double> Frequency => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public BindableNumber<double> Tempo => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IBindable<double> GetAggregate(AdjustableProperty type) => throw new NotImplementedException();
|
||||||
|
|
||||||
public IBindable<double> AggregateVolume => throw new NotImplementedException();
|
public IBindable<double> AggregateVolume => throw new NotImplementedException();
|
||||||
|
|
||||||
@ -527,6 +531,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
public IBindable<double> AggregateFrequency => throw new NotImplementedException();
|
public IBindable<double> AggregateFrequency => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IBindable<double> AggregateTempo => throw new NotImplementedException();
|
||||||
|
|
||||||
public int PlaybackConcurrency
|
public int PlaybackConcurrency
|
||||||
{
|
{
|
||||||
get => throw new NotImplementedException();
|
get => throw new NotImplementedException();
|
||||||
|
@ -14,12 +14,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a part of the summary timeline..
|
/// Represents a part of the summary timeline..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class TimelinePart : CompositeDrawable
|
public abstract class TimelinePart : Container
|
||||||
{
|
{
|
||||||
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
private readonly Container timeline;
|
private readonly Container timeline;
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => timeline;
|
||||||
|
|
||||||
protected TimelinePart()
|
protected TimelinePart()
|
||||||
{
|
{
|
||||||
AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both });
|
AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
@ -50,8 +52,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
|
timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void Add(Drawable visualisation) => timeline.Add(visualisation);
|
|
||||||
|
|
||||||
protected virtual void LoadBeatmap(WorkingBeatmap beatmap)
|
protected virtual void LoadBeatmap(WorkingBeatmap beatmap)
|
||||||
{
|
{
|
||||||
timeline.Clear();
|
timeline.Clear();
|
||||||
|
@ -5,19 +5,18 @@ using System;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
|
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
|
||||||
{
|
{
|
||||||
protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition)
|
protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
|
||||||
: base(hitObject, nextHitObject, centrePosition)
|
: base(startPosition, startTime, endTime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateContent(Vector2 centrePosition)
|
protected override void CreateContent(Vector2 startPosition)
|
||||||
{
|
{
|
||||||
const float crosshair_thickness = 1;
|
const float crosshair_thickness = 1;
|
||||||
const float crosshair_max_size = 10;
|
const float crosshair_max_size = 10;
|
||||||
@ -27,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = centrePosition,
|
Position = startPosition,
|
||||||
Width = crosshair_thickness,
|
Width = crosshair_thickness,
|
||||||
EdgeSmoothness = new Vector2(1),
|
EdgeSmoothness = new Vector2(1),
|
||||||
Height = Math.Min(crosshair_max_size, DistanceSpacing * 2),
|
Height = Math.Min(crosshair_max_size, DistanceSpacing * 2),
|
||||||
@ -35,15 +34,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = centrePosition,
|
Position = startPosition,
|
||||||
EdgeSmoothness = new Vector2(1),
|
EdgeSmoothness = new Vector2(1),
|
||||||
Width = Math.Min(crosshair_max_size, DistanceSpacing * 2),
|
Width = Math.Min(crosshair_max_size, DistanceSpacing * 2),
|
||||||
Height = crosshair_thickness,
|
Height = crosshair_thickness,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X);
|
float dx = Math.Max(startPosition.X, DrawWidth - startPosition.X);
|
||||||
float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y);
|
float dy = Math.Max(startPosition.Y, DrawHeight - startPosition.Y);
|
||||||
float maxDistance = new Vector2(dx, dy).Length;
|
float maxDistance = new Vector2(dx, dy).Length;
|
||||||
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing));
|
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing));
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
AddInternal(new CircularProgress
|
AddInternal(new CircularProgress
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = centrePosition,
|
Position = startPosition,
|
||||||
Current = { Value = 1 },
|
Current = { Value = 1 },
|
||||||
Size = new Vector2(radius),
|
Size = new Vector2(radius),
|
||||||
InnerRadius = 4 * 1f / radius,
|
InnerRadius = 4 * 1f / radius,
|
||||||
@ -66,9 +65,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position)
|
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position)
|
||||||
{
|
{
|
||||||
if (MaxIntervals == 0)
|
if (MaxIntervals == 0)
|
||||||
return (CentrePosition, StartTime);
|
return (StartPosition, StartTime);
|
||||||
|
|
||||||
Vector2 direction = position - CentrePosition;
|
Vector2 direction = position - StartPosition;
|
||||||
if (direction == Vector2.Zero)
|
if (direction == Vector2.Zero)
|
||||||
direction = new Vector2(0.001f, 0.001f);
|
direction = new Vector2(0.001f, 0.001f);
|
||||||
|
|
||||||
@ -78,9 +77,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals);
|
int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals);
|
||||||
|
|
||||||
Vector2 normalisedDirection = direction * new Vector2(1f / distance);
|
Vector2 normalisedDirection = direction * new Vector2(1f / distance);
|
||||||
Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius;
|
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
|
||||||
|
|
||||||
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length));
|
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -9,7 +8,6 @@ using osu.Framework.Graphics.Colour;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
@ -24,21 +22,21 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected float DistanceSpacing { get; private set; }
|
protected float DistanceSpacing { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The snapping time at <see cref="CentrePosition"/>.
|
|
||||||
/// </summary>
|
|
||||||
protected double StartTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum number of distance snapping intervals allowed.
|
/// The maximum number of distance snapping intervals allowed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected int MaxIntervals { get; private set; }
|
protected int MaxIntervals { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position which the grid is centred on.
|
/// The position which the grid should start.
|
||||||
/// The first beat snapping tick is located at <see cref="CentrePosition"/> + <see cref="DistanceSpacing"/> in the desired direction.
|
/// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceSpacing"/> away from this point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly Vector2 CentrePosition;
|
protected readonly Vector2 StartPosition;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The snapping time at <see cref="StartPosition"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly double StartTime;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
protected OsuColour Colours { get; private set; }
|
protected OsuColour Colours { get; private set; }
|
||||||
@ -53,25 +51,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private BindableBeatDivisor beatDivisor { get; set; }
|
private BindableBeatDivisor beatDivisor { get; set; }
|
||||||
|
|
||||||
private readonly Cached gridCache = new Cached();
|
private readonly Cached gridCache = new Cached();
|
||||||
private readonly HitObject hitObject;
|
private readonly double? endTime;
|
||||||
private readonly HitObject nextHitObject;
|
|
||||||
|
|
||||||
protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition)
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="DistanceSnapGrid"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
|
||||||
|
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
|
||||||
|
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
|
||||||
|
protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.endTime = endTime;
|
||||||
this.nextHitObject = nextHitObject;
|
StartPosition = startPosition;
|
||||||
|
StartTime = startTime;
|
||||||
CentrePosition = centrePosition;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
StartTime = hitObject.GetEndTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -83,12 +79,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
|
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
|
||||||
|
|
||||||
if (nextHitObject == null)
|
if (endTime == null)
|
||||||
MaxIntervals = int.MaxValue;
|
MaxIntervals = int.MaxValue;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
|
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
|
||||||
double maxDuration = nextHitObject.StartTime - StartTime + 1;
|
double maxDuration = endTime.Value - StartTime + 1;
|
||||||
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
|
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +106,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (!gridCache.IsValid)
|
if (!gridCache.IsValid)
|
||||||
{
|
{
|
||||||
ClearInternal();
|
ClearInternal();
|
||||||
CreateContent(CentrePosition);
|
CreateContent(StartPosition);
|
||||||
gridCache.Validate();
|
gridCache.Validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +114,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the content which visualises the grid ticks.
|
/// Creates the content which visualises the grid ticks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract void CreateContent(Vector2 centrePosition);
|
protected abstract void CreateContent(Vector2 startPosition);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Snaps a position to this grid.
|
/// Snaps a position to this grid.
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
{
|
{
|
||||||
this.adjustableClock = adjustableClock;
|
this.adjustableClock = adjustableClock;
|
||||||
|
|
||||||
Child = waveform = new WaveformGraph
|
Add(waveform = new WaveformGraph
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colours.Blue.Opacity(0.2f),
|
Colour = colours.Blue.Opacity(0.2f),
|
||||||
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
MidColour = colours.BlueDark,
|
MidColour = colours.BlueDark,
|
||||||
HighColour = colours.BlueDarker,
|
HighColour = colours.BlueDarker,
|
||||||
Depth = float.MaxValue
|
Depth = float.MaxValue
|
||||||
};
|
});
|
||||||
|
|
||||||
// We don't want the centre marker to scroll
|
// We don't want the centre marker to scroll
|
||||||
AddInternal(new CentreMarker());
|
AddInternal(new CentreMarker());
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
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.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -11,17 +12,18 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
{
|
{
|
||||||
public class TimelineArea : CompositeDrawable
|
public class TimelineArea : Container
|
||||||
{
|
{
|
||||||
private readonly Timeline timeline;
|
private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
public TimelineArea()
|
protected override Container<Drawable> Content => timeline;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
|
|
||||||
OsuCheckbox hitObjectsCheckbox;
|
|
||||||
OsuCheckbox hitSoundsCheckbox;
|
|
||||||
OsuCheckbox waveformCheckbox;
|
OsuCheckbox waveformCheckbox;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
@ -60,8 +62,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Spacing = new Vector2(0, 4),
|
Spacing = new Vector2(0, 4),
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hit objects" },
|
|
||||||
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hit sounds" },
|
|
||||||
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
|
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
timeline = new Timeline { RelativeSizeAxes = Axes.Both }
|
timeline
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ColumnDimensions = new[]
|
ColumnDimensions = new[]
|
||||||
@ -119,8 +119,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
hitObjectsCheckbox.Current.Value = true;
|
|
||||||
hitSoundsCheckbox.Current.Value = true;
|
|
||||||
waveformCheckbox.Current.Value = true;
|
waveformCheckbox.Current.Value = true;
|
||||||
|
|
||||||
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
|
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||||
|
{
|
||||||
|
internal class TimelineHitObjectDisplay : TimelinePart
|
||||||
|
{
|
||||||
|
private IEditorBeatmap beatmap { get; }
|
||||||
|
|
||||||
|
public TimelineHitObjectDisplay(IEditorBeatmap beatmap)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
foreach (var h in beatmap.HitObjects)
|
||||||
|
add(h);
|
||||||
|
|
||||||
|
beatmap.HitObjectAdded += add;
|
||||||
|
beatmap.HitObjectRemoved += remove;
|
||||||
|
beatmap.StartTimeChanged += h =>
|
||||||
|
{
|
||||||
|
remove(h);
|
||||||
|
add(h);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(HitObject h)
|
||||||
|
{
|
||||||
|
foreach (var d in Children.OfType<TimelineHitObjectRepresentation>().Where(c => c.HitObject == h))
|
||||||
|
d.Expire();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(HitObject h)
|
||||||
|
{
|
||||||
|
var yOffset = Children.Count(d => d.X == h.StartTime);
|
||||||
|
|
||||||
|
Add(new TimelineHitObjectRepresentation(h) { Y = -yOffset * TimelineHitObjectRepresentation.THICKNESS });
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimelineHitObjectRepresentation : CompositeDrawable
|
||||||
|
{
|
||||||
|
public const float THICKNESS = 3;
|
||||||
|
|
||||||
|
public readonly HitObject HitObject;
|
||||||
|
|
||||||
|
public TimelineHitObjectRepresentation(HitObject hitObject)
|
||||||
|
{
|
||||||
|
HitObject = hitObject;
|
||||||
|
Anchor = Anchor.CentreLeft;
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
|
Width = (float)(hitObject.GetEndTime() - hitObject.StartTime);
|
||||||
|
|
||||||
|
X = (float)hitObject.StartTime;
|
||||||
|
|
||||||
|
RelativePositionAxes = Axes.X;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
if (hitObject is IHasEndTime)
|
||||||
|
{
|
||||||
|
AddInternal(new Container
|
||||||
|
{
|
||||||
|
CornerRadius = 2,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(1, THICKNESS),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Colour = Color4.Black,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AddInternal(new Circle
|
||||||
|
{
|
||||||
|
Size = new Vector2(16),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
Colour = Color4.White,
|
||||||
|
BorderColour = Color4.Black,
|
||||||
|
BorderThickness = THICKNESS,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,32 +3,35 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose
|
namespace osu.Game.Screens.Edit.Compose
|
||||||
{
|
{
|
||||||
public class ComposeScreen : EditorScreenWithTimeline
|
public class ComposeScreen : EditorScreenWithTimeline
|
||||||
{
|
{
|
||||||
|
private HitObjectComposer composer;
|
||||||
|
|
||||||
protected override Drawable CreateMainContent()
|
protected override Drawable CreateMainContent()
|
||||||
{
|
{
|
||||||
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
|
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
|
||||||
|
composer = ruleset?.CreateHitObjectComposer();
|
||||||
|
|
||||||
var composer = ruleset?.CreateHitObjectComposer();
|
if (ruleset == null || composer == null)
|
||||||
|
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
|
||||||
|
|
||||||
if (composer != null)
|
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
||||||
{
|
|
||||||
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
|
|
||||||
|
|
||||||
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
|
||||||
// full access to all skin sources.
|
// full access to all skin sources.
|
||||||
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
|
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
|
||||||
|
|
||||||
// load the skinning hierarchy first.
|
// load the skinning hierarchy first.
|
||||||
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
|
||||||
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer()));
|
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
|
||||||
}
|
|
||||||
|
|
||||||
return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateTimelineContent() => new TimelineHitObjectDisplay(composer.EditorBeatmap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||||
|
|
||||||
|
private TimelineArea timelineArea;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
|
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
|
||||||
{
|
{
|
||||||
@ -64,7 +66,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = 5 },
|
Padding = new MarginPadding { Right = 5 },
|
||||||
Child = CreateTimeline()
|
Child = timelineArea = CreateTimelineArea()
|
||||||
},
|
},
|
||||||
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
|
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
@ -97,11 +99,15 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
mainContent.Add(content);
|
mainContent.Add(content);
|
||||||
content.FadeInFromZero(300, Easing.OutQuint);
|
content.FadeInFromZero(300, Easing.OutQuint);
|
||||||
|
|
||||||
|
LoadComponentAsync(CreateTimelineContent(), timelineArea.Add);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Drawable CreateMainContent();
|
protected abstract Drawable CreateMainContent();
|
||||||
|
|
||||||
protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both };
|
protected virtual Drawable CreateTimelineContent() => new Container();
|
||||||
|
|
||||||
|
protected TimelineArea CreateTimelineArea() => new TimelineArea { RelativeSizeAxes = Axes.Both };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// Invoked when a <see cref="HitObject"/> is removed from this <see cref="IEditorBeatmap"/>.
|
/// Invoked when a <see cref="HitObject"/> is removed from this <see cref="IEditorBeatmap"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<HitObject> HitObjectRemoved;
|
event Action<HitObject> HitObjectRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the start time of a <see cref="HitObject"/> in this <see cref="EditorBeatmap{T}"/> was changed.
|
||||||
|
/// </summary>
|
||||||
|
event Action<HitObject> StartTimeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -28,9 +28,9 @@ namespace osu.Game.Screens.Play
|
|||||||
private readonly IReadOnlyList<Mod> mods;
|
private readonly IReadOnlyList<Mod> mods;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track).
|
/// The <see cref="WorkingBeatmap"/>'s track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IAdjustableClock sourceClock;
|
private Track track;
|
||||||
|
|
||||||
public readonly BindableBool IsPaused = new BindableBool();
|
public readonly BindableBool IsPaused = new BindableBool();
|
||||||
|
|
||||||
@ -72,8 +72,8 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock();
|
track = beatmap.Track;
|
||||||
(sourceClock as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
||||||
|
|
||||||
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
|
||||||
|
|
||||||
@ -127,11 +127,11 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
sourceClock.Reset();
|
track.Reset();
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
adjustableClock.ChangeSource(sourceClock);
|
adjustableClock.ChangeSource(track);
|
||||||
updateRate();
|
updateRate();
|
||||||
|
|
||||||
if (!IsPaused.Value)
|
if (!IsPaused.Value)
|
||||||
@ -197,13 +197,13 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void StopUsingBeatmapClock()
|
public void StopUsingBeatmapClock()
|
||||||
{
|
{
|
||||||
if (sourceClock != beatmap.Track)
|
if (track != beatmap.Track)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
removeSourceClockAdjustments();
|
removeSourceClockAdjustments();
|
||||||
|
|
||||||
sourceClock = new TrackVirtual(beatmap.Track.Length);
|
track = new TrackVirtual(beatmap.Track.Length);
|
||||||
adjustableClock.ChangeSource(sourceClock);
|
adjustableClock.ChangeSource(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -218,18 +218,15 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private void updateRate()
|
private void updateRate()
|
||||||
{
|
{
|
||||||
if (sourceClock == null) return;
|
if (track == null) return;
|
||||||
|
|
||||||
speedAdjustmentsApplied = true;
|
speedAdjustmentsApplied = true;
|
||||||
sourceClock.ResetSpeedAdjustments();
|
track.ResetSpeedAdjustments();
|
||||||
|
|
||||||
if (sourceClock is IHasTempoAdjust tempo)
|
track.Tempo.Value = UserPlaybackRate.Value;
|
||||||
tempo.TempoAdjust = UserPlaybackRate.Value;
|
|
||||||
else
|
|
||||||
sourceClock.Rate = UserPlaybackRate.Value;
|
|
||||||
|
|
||||||
foreach (var mod in mods.OfType<IApplicableToClock>())
|
foreach (var mod in mods.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToClock(sourceClock);
|
mod.ApplyToTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@ -237,18 +234,18 @@ namespace osu.Game.Screens.Play
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
removeSourceClockAdjustments();
|
removeSourceClockAdjustments();
|
||||||
sourceClock = null;
|
track = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeSourceClockAdjustments()
|
private void removeSourceClockAdjustments()
|
||||||
{
|
{
|
||||||
if (speedAdjustmentsApplied)
|
if (speedAdjustmentsApplied)
|
||||||
{
|
{
|
||||||
sourceClock.ResetSpeedAdjustments();
|
track.ResetSpeedAdjustments();
|
||||||
speedAdjustmentsApplied = false;
|
speedAdjustmentsApplied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
(sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,26 +188,22 @@ namespace osu.Game.Screens.Play
|
|||||||
InternalButtons.Add(button);
|
InternalButtons.Add(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _selectionIndex = -1;
|
private int selectionIndex = -1;
|
||||||
|
|
||||||
private int selectionIndex
|
private void setSelected(int value)
|
||||||
{
|
{
|
||||||
get => _selectionIndex;
|
if (selectionIndex == value)
|
||||||
set
|
return;
|
||||||
{
|
|
||||||
if (_selectionIndex == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Deselect the previously-selected button
|
// Deselect the previously-selected button
|
||||||
if (_selectionIndex != -1)
|
if (selectionIndex != -1)
|
||||||
InternalButtons[_selectionIndex].Selected.Value = false;
|
InternalButtons[selectionIndex].Selected.Value = false;
|
||||||
|
|
||||||
_selectionIndex = value;
|
selectionIndex = value;
|
||||||
|
|
||||||
// Select the newly-selected button
|
// Select the newly-selected button
|
||||||
if (_selectionIndex != -1)
|
if (selectionIndex != -1)
|
||||||
InternalButtons[_selectionIndex].Selected.Value = true;
|
InternalButtons[selectionIndex].Selected.Value = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
@ -218,16 +214,16 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
case Key.Up:
|
case Key.Up:
|
||||||
if (selectionIndex == -1 || selectionIndex == 0)
|
if (selectionIndex == -1 || selectionIndex == 0)
|
||||||
selectionIndex = InternalButtons.Count - 1;
|
setSelected(InternalButtons.Count - 1);
|
||||||
else
|
else
|
||||||
selectionIndex--;
|
setSelected(selectionIndex - 1);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case Key.Down:
|
case Key.Down:
|
||||||
if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1)
|
if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1)
|
||||||
selectionIndex = 0;
|
setSelected(0);
|
||||||
else
|
else
|
||||||
selectionIndex++;
|
setSelected(selectionIndex + 1);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -266,9 +262,9 @@ namespace osu.Game.Screens.Play
|
|||||||
private void buttonSelectionChanged(DialogButton button, bool isSelected)
|
private void buttonSelectionChanged(DialogButton button, bool isSelected)
|
||||||
{
|
{
|
||||||
if (!isSelected)
|
if (!isSelected)
|
||||||
selectionIndex = -1;
|
setSelected(-1);
|
||||||
else
|
else
|
||||||
selectionIndex = InternalButtons.IndexOf(button);
|
setSelected(InternalButtons.IndexOf(button));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRetryCount()
|
private void updateRetryCount()
|
||||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play
|
|||||||
addGameplayComponents(GameplayClockContainer, working);
|
addGameplayComponents(GameplayClockContainer, working);
|
||||||
addOverlayComponents(GameplayClockContainer, working);
|
addOverlayComponents(GameplayClockContainer, working);
|
||||||
|
|
||||||
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
|
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
|
||||||
|
|
||||||
// bind clock into components that require it
|
// bind clock into components that require it
|
||||||
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
|
||||||
@ -146,6 +146,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
mod.ApplyToScoreProcessor(ScoreProcessor);
|
mod.ApplyToScoreProcessor(ScoreProcessor);
|
||||||
|
breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addUnderlayComponents(Container target)
|
private void addUnderlayComponents(Container target)
|
||||||
@ -241,6 +242,11 @@ namespace osu.Game.Screens.Play
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updatePauseOnFocusLostState() =>
|
||||||
|
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
|
||||||
|
&& !DrawableRuleset.HasReplayLoaded.Value
|
||||||
|
&& !breakOverlay.IsBreakTime.Value;
|
||||||
|
|
||||||
private WorkingBeatmap loadBeatmap()
|
private WorkingBeatmap loadBeatmap()
|
||||||
{
|
{
|
||||||
WorkingBeatmap working = Beatmap.Value;
|
WorkingBeatmap working = Beatmap.Value;
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
||||||
where T : struct, IComparable
|
where T : struct
|
||||||
{
|
{
|
||||||
public bool HasFilter => Max != null || Min != null;
|
public bool HasFilter => Max != null || Min != null;
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
|
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
|
||||||
where T : struct, IComparable
|
where T : struct
|
||||||
{
|
{
|
||||||
switch (op)
|
switch (op)
|
||||||
{
|
{
|
||||||
|
@ -262,8 +262,10 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
|
||||||
{
|
{
|
||||||
if (this.IsCurrentScreen())
|
// if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter).
|
||||||
Carousel.Filter(criteria);
|
bool shouldDebounce = this.IsCurrentScreen();
|
||||||
|
|
||||||
|
Schedule(() => Carousel.Filter(criteria, shouldDebounce));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
private DependencyContainer dependencies;
|
||||||
@ -437,8 +439,6 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
base.OnEntering(last);
|
base.OnEntering(last);
|
||||||
|
|
||||||
Carousel.Filter(FilterControl.CreateCriteria(), false);
|
|
||||||
|
|
||||||
this.FadeInFromZero(250);
|
this.FadeInFromZero(250);
|
||||||
FilterControl.Activate();
|
FilterControl.Activate();
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
public class SkinConfigManager<T> : ConfigManager<T> where T : struct
|
public class SkinConfigManager<TLookup> : ConfigManager<TLookup> where TLookup : struct, Enum
|
||||||
{
|
{
|
||||||
protected override void PerformLoad()
|
protected override void PerformLoad()
|
||||||
{
|
{
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||||
public static TransformSequence<T> TransformFlipH<T>(this T flippable, bool newValue, double delay = 0)
|
public static TransformSequence<T> TransformFlipH<T>(this T flippable, bool newValue, double delay = 0)
|
||||||
where T : IFlippable
|
where T : class, IFlippable
|
||||||
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay));
|
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -49,7 +49,7 @@ namespace osu.Game.Storyboards.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
|
||||||
public static TransformSequence<T> TransformFlipV<T>(this T flippable, bool newValue, double delay = 0)
|
public static TransformSequence<T> TransformFlipV<T>(this T flippable, bool newValue, double delay = 0)
|
||||||
where T : IFlippable
|
where T : class, IFlippable
|
||||||
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay));
|
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
// note that this will override any mod rate application
|
// note that this will override any mod rate application
|
||||||
Beatmap.Value.Track.TempoAdjust = Clock.Rate;
|
Beatmap.Value.Track.Tempo.Value = Clock.Rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ScreenTestScene : ManualInputManagerTestScene
|
public abstract class ScreenTestScene : ManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly OsuScreenStack stack;
|
protected readonly OsuScreenStack Stack;
|
||||||
|
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
@ -22,16 +22,16 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
base.Content.AddRange(new Drawable[]
|
base.Content.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
|
||||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void LoadScreen(OsuScreen screen)
|
protected void LoadScreen(OsuScreen screen)
|
||||||
{
|
{
|
||||||
if (stack.CurrentScreen != null)
|
if (Stack.CurrentScreen != null)
|
||||||
stack.Exit();
|
Stack.Exit();
|
||||||
stack.Push(screen);
|
Stack.Push(screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,16 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
public class TestPlayer : Player
|
public class TestPlayer : Player
|
||||||
{
|
{
|
||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost { get; }
|
||||||
|
|
||||||
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
|
||||||
|
|
||||||
public TestPlayer(bool allowPause = true, bool showResults = true)
|
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
|
||||||
|
|
||||||
|
public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
|
||||||
: base(allowPause, showResults)
|
: base(allowPause, showResults)
|
||||||
{
|
{
|
||||||
|
PauseOnFocusLost = pauseOnFocusLost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user