mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 16:02:58 +08:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into rankings-tables
This commit is contained in:
commit
0f53725005
@ -62,6 +62,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.1029.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1106.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
editorClock = clock;
|
editorClock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleMovement(MoveSelectionEvent moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
|
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
|
||||||
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
|
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
|
||||||
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
performDragMovement(moveEvent);
|
performDragMovement(moveEvent);
|
||||||
performColumnMovement(lastColumn, moveEvent);
|
performColumnMovement(lastColumn, moveEvent);
|
||||||
|
|
||||||
base.HandleMovement(moveEvent);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
230
osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
Normal file
230
osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneFollowPoints : OsuTestScene
|
||||||
|
{
|
||||||
|
private Container<DrawableOsuHitObject> hitObjectContainer;
|
||||||
|
private FollowPointRenderer followPointRenderer;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both },
|
||||||
|
followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddObject()
|
||||||
|
{
|
||||||
|
addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveObject()
|
||||||
|
{
|
||||||
|
addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(0));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddMultipleObjects()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveEndObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(4));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveStartObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(0));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveMiddleObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(2));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(0, 0)] // Start -> Start
|
||||||
|
[TestCase(0, 2)] // Start -> Middle
|
||||||
|
[TestCase(0, 5)] // Start -> End
|
||||||
|
[TestCase(2, 0)] // Middle -> Start
|
||||||
|
[TestCase(1, 3)] // Middle -> Middle (forwards)
|
||||||
|
[TestCase(3, 1)] // Middle -> Middle (backwards)
|
||||||
|
[TestCase(4, 0)] // End -> Start
|
||||||
|
[TestCase(4, 2)] // End -> Middle
|
||||||
|
[TestCase(4, 4)] // End -> End
|
||||||
|
public void TestReorderObjects(int startIndex, int endIndex)
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
reorderObjectStep(startIndex, endIndex);
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[]
|
||||||
|
{
|
||||||
|
new HitCircle { Position = new Vector2(100, 100) },
|
||||||
|
new HitCircle { Position = new Vector2(200, 200) },
|
||||||
|
new HitCircle { Position = new Vector2(300, 300) },
|
||||||
|
new HitCircle { Position = new Vector2(400, 400) },
|
||||||
|
new HitCircle { Position = new Vector2(500, 500) },
|
||||||
|
});
|
||||||
|
|
||||||
|
private void addObjectsStep(Func<OsuHitObject[]> ctorFunc)
|
||||||
|
{
|
||||||
|
AddStep("add hitobjects", () =>
|
||||||
|
{
|
||||||
|
var objects = ctorFunc();
|
||||||
|
|
||||||
|
for (int i = 0; i < objects.Length; i++)
|
||||||
|
{
|
||||||
|
objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1);
|
||||||
|
objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
DrawableOsuHitObject drawableObject = null;
|
||||||
|
|
||||||
|
switch (objects[i])
|
||||||
|
{
|
||||||
|
case HitCircle circle:
|
||||||
|
drawableObject = new DrawableHitCircle(circle);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Slider slider:
|
||||||
|
drawableObject = new DrawableSlider(slider);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Spinner spinner:
|
||||||
|
drawableObject = new DrawableSpinner(spinner);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitObjectContainer.Add(drawableObject);
|
||||||
|
followPointRenderer.AddFollowPoints(drawableObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeObjectStep(Func<DrawableOsuHitObject> getFunc)
|
||||||
|
{
|
||||||
|
AddStep("remove hitobject", () =>
|
||||||
|
{
|
||||||
|
var drawableObject = getFunc?.Invoke();
|
||||||
|
|
||||||
|
hitObjectContainer.Remove(drawableObject);
|
||||||
|
followPointRenderer.RemoveFollowPoints(drawableObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reorderObjectStep(int startIndex, int endIndex)
|
||||||
|
{
|
||||||
|
AddStep($"move object {startIndex} to {endIndex}", () =>
|
||||||
|
{
|
||||||
|
DrawableOsuHitObject toReorder = getObject(startIndex);
|
||||||
|
|
||||||
|
double targetTime;
|
||||||
|
if (endIndex < hitObjectContainer.Count)
|
||||||
|
targetTime = getObject(endIndex).HitObject.StartTime - 1;
|
||||||
|
else
|
||||||
|
targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1;
|
||||||
|
|
||||||
|
hitObjectContainer.Remove(toReorder);
|
||||||
|
toReorder.HitObject.StartTime = targetTime;
|
||||||
|
hitObjectContainer.Add(toReorder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertGroups()
|
||||||
|
{
|
||||||
|
AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
|
||||||
|
AddAssert("group endpoints are correct", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hitObjectContainer.Count; i++)
|
||||||
|
{
|
||||||
|
DrawableOsuHitObject expectedStart = getObject(i);
|
||||||
|
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
|
||||||
|
|
||||||
|
if (getGroup(i).Start != expectedStart)
|
||||||
|
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
|
||||||
|
|
||||||
|
if (getGroup(i).End != expectedEnd)
|
||||||
|
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
|
||||||
|
|
||||||
|
private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
|
||||||
|
|
||||||
|
private class TestHitObjectContainer : Container<DrawableOsuHitObject>
|
||||||
|
{
|
||||||
|
protected override int Compare(Drawable x, Drawable y)
|
||||||
|
{
|
||||||
|
var osuX = (DrawableOsuHitObject)x;
|
||||||
|
var osuY = (DrawableOsuHitObject)y;
|
||||||
|
|
||||||
|
int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime);
|
||||||
|
|
||||||
|
if (compare == 0)
|
||||||
|
return base.Compare(x, y);
|
||||||
|
|
||||||
|
return compare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,11 +42,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Cached(typeof(IDistanceSnapProvider))]
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
private readonly SnapProvider snapProvider = new SnapProvider();
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
private readonly TestOsuDistanceSnapGrid grid;
|
private TestOsuDistanceSnapGrid grid;
|
||||||
|
|
||||||
public TestSceneOsuDistanceSnapGrid()
|
public TestSceneOsuDistanceSnapGrid()
|
||||||
{
|
{
|
||||||
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||||
|
editorBeatmap.ControlPointInfo.Clear();
|
||||||
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -58,14 +66,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
||||||
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup() => Schedule(() =>
|
|
||||||
{
|
|
||||||
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
|
||||||
editorBeatmap.ControlPointInfo.Clear();
|
|
||||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -102,6 +102,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
assertSnappedDistance((float)beat_length * 2);
|
assertSnappedDistance((float)beat_length * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLimitedDistance()
|
||||||
|
{
|
||||||
|
AddStep("create limited grid", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
|
||||||
|
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f)));
|
||||||
|
assertSnappedDistance((float)beat_length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
|
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
|
||||||
{
|
{
|
||||||
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
|
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
|
||||||
@ -152,8 +173,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public new float DistanceSpacing => base.DistanceSpacing;
|
public new float DistanceSpacing => base.DistanceSpacing;
|
||||||
|
|
||||||
public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
|
public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null)
|
||||||
: base(hitObject)
|
: base(hitObject, nextHitObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,9 +185,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
|
||||||
public float DurationToDistance(double referenceTime, double duration) => 0;
|
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
|
||||||
|
|
||||||
public double DistanceToDuration(double referenceTime, float distance) => 0;
|
public double DistanceToDuration(double referenceTime, float distance) => distance;
|
||||||
|
|
||||||
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
||||||
|
|
||||||
|
@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private readonly Container marker;
|
private readonly Container marker;
|
||||||
private readonly Drawable markerRing;
|
private readonly Drawable markerRing;
|
||||||
|
|
||||||
private bool isClicked;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
@ -101,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
||||||
if (IsHovered || isClicked || IsSelected.Value)
|
if (IsHovered || IsSelected.Value)
|
||||||
colour = Color4.White;
|
colour = Color4.White;
|
||||||
marker.Colour = colour;
|
marker.Colour = colour;
|
||||||
}
|
}
|
||||||
@ -127,21 +125,18 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
isClicked = true;
|
if (RequestSelection != null)
|
||||||
return true;
|
{
|
||||||
|
RequestSelection.Invoke(Index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseUp(MouseUpEvent e)
|
protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
|
||||||
{
|
|
||||||
isClicked = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
{
|
|
||||||
RequestSelection?.Invoke(Index);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||||
|
|
||||||
|
@ -2,27 +2,36 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointVisualiser : CompositeDrawable
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||||
{
|
{
|
||||||
public Action<Vector2[]> ControlPointsChanged;
|
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 InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider)
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IPlacementHandler placementHandler { get; set; }
|
||||||
|
|
||||||
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
|
this.allowSelection = allowSelection;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
@ -42,11 +51,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
||||||
{
|
{
|
||||||
Pieces.Add(new PathControlPointPiece(slider, Pieces.Count)
|
var piece = new PathControlPointPiece(slider, Pieces.Count)
|
||||||
{
|
{
|
||||||
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
||||||
RequestSelection = selectPiece
|
};
|
||||||
});
|
|
||||||
|
if (allowSelection)
|
||||||
|
piece.RequestSelection = selectPiece;
|
||||||
|
|
||||||
|
Pieces.Add(piece);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
||||||
@ -70,5 +83,51 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
piece.IsSelected.Value = piece.Index == index;
|
piece.IsSelected.Value = piece.Index == index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(PlatformAction action)
|
||||||
|
{
|
||||||
|
switch (action.ActionMethod)
|
||||||
|
{
|
||||||
|
case PlatformActionMethod.Delete:
|
||||||
|
var newControlPoints = new List<Vector2>();
|
||||||
|
|
||||||
|
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
|
||||||
|
if (newControlPoints.Count == slider.Path.ControlPoints.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If there are 0 remaining control points, treat the slider as being deleted
|
||||||
|
if (newControlPoints.Count == 0)
|
||||||
|
{
|
||||||
|
placementHandler?.Delete(slider);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make control points relative
|
||||||
|
Vector2 first = newControlPoints[0];
|
||||||
|
for (int i = 0; i < newControlPoints.Count; i++)
|
||||||
|
newControlPoints[i] = newControlPoints[i] - 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 = slider.Position + first;
|
||||||
|
|
||||||
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
|
||||||
|
ControlPointsChanged?.Invoke(newControlPoints.ToArray());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,5 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
Size = body.Size;
|
Size = body.Size;
|
||||||
OriginPosition = body.PathOffset;
|
OriginPosition = body.PathOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
bodyPiece = new SliderBodyPiece(),
|
bodyPiece = new SliderBodyPiece(),
|
||||||
headCirclePiece = new HitCirclePiece(),
|
headCirclePiece = new HitCirclePiece(),
|
||||||
tailCirclePiece = new HitCirclePiece(),
|
tailCirclePiece = new HitCirclePiece(),
|
||||||
new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
|
new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() },
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
|
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
BodyPiece = new SliderBodyPiece(),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||||
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||||
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
|
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
{
|
{
|
||||||
public OsuDistanceSnapGrid(OsuHitObject hitObject)
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
|
||||||
: base(hitObject, hitObject.StackedEndPosition)
|
: base(hitObject, nextHitObject, hitObject.StackedEndPosition)
|
||||||
{
|
{
|
||||||
Masking = true;
|
Masking = true;
|
||||||
}
|
}
|
||||||
|
@ -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.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -60,25 +61,40 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
var objects = selectedHitObjects.ToList();
|
var objects = selectedHitObjects.ToList();
|
||||||
|
|
||||||
if (objects.Count == 0)
|
if (objects.Count == 0)
|
||||||
|
return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
|
||||||
|
|
||||||
|
double minTime = objects.Min(h => h.StartTime);
|
||||||
|
return createGrid(h => h.StartTime < minTime, objects.Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a grid from the last <see cref="HitObject"/> matching a predicate to a target <see cref="HitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceSelector">A predicate that matches <see cref="HitObject"/>s where the grid can start from.
|
||||||
|
/// Only the last <see cref="HitObject"/> matching the predicate is used.</param>
|
||||||
|
/// <param name="targetOffset">An offset from the <see cref="HitObject"/> selected via <paramref name="sourceSelector"/> at which the grid should stop.</param>
|
||||||
|
/// <returns>The <see cref="OsuDistanceSnapGrid"/> from a selected <see cref="HitObject"/> to a target <see cref="HitObject"/>.</returns>
|
||||||
|
private OsuDistanceSnapGrid createGrid(Func<HitObject, bool> sourceSelector, int targetOffset = 1)
|
||||||
|
{
|
||||||
|
if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset));
|
||||||
|
|
||||||
|
int sourceIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
|
if (!sourceSelector(EditorBeatmap.HitObjects[i]))
|
||||||
|
break;
|
||||||
|
|
||||||
if (lastObject == null)
|
sourceIndex = i;
|
||||||
return null;
|
|
||||||
|
|
||||||
return new OsuDistanceSnapGrid(lastObject);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
double minTime = objects.Min(h => h.StartTime);
|
|
||||||
|
|
||||||
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
|
if (sourceIndex == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (lastObject == null)
|
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
|
||||||
return null;
|
OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
|
||||||
|
|
||||||
return new OsuDistanceSnapGrid(lastObject);
|
return new OsuDistanceSnapGrid(sourceObject, targetObject);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,34 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
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;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class OsuSelectionHandler : SelectionHandler
|
public class OsuSelectionHandler : SelectionHandler
|
||||||
{
|
{
|
||||||
public override void HandleMovement(MoveSelectionEvent moveEvent)
|
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
|
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
|
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
|
||||||
|
|
||||||
|
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
|
||||||
|
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
if (h is Spinner)
|
||||||
|
{
|
||||||
|
// Spinners don't support position adjustments
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stacking is not considered
|
||||||
|
minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
|
||||||
|
maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight)
|
||||||
|
return false;
|
||||||
|
|
||||||
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
||||||
{
|
{
|
||||||
if (h is Spinner)
|
if (h is Spinner)
|
||||||
@ -22,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
h.Position += moveEvent.InstantDelta;
|
h.Position += moveEvent.InstantDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.HandleMovement(moveEvent);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Connects hit objects visually, for example with follow points.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ConnectionRenderer<T> : LifetimeManagementContainer
|
|
||||||
where T : HitObject
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Hit objects to create connections for
|
|
||||||
/// </summary>
|
|
||||||
public abstract IEnumerable<T> HitObjects { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,9 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
public class FollowPoint : Container
|
public class FollowPoint : Container
|
||||||
{
|
{
|
||||||
private const float width = 8;
|
private const float width = 8;
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
// 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class FollowPointConnection : CompositeDrawable
|
||||||
|
{
|
||||||
|
// Todo: These shouldn't be constants
|
||||||
|
private const int spacing = 32;
|
||||||
|
private const double preempt = 800;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start time of <see cref="Start"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<double> StartTime = new Bindable<double>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull]
|
||||||
|
public readonly DrawableOsuHitObject Start;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="FollowPointConnection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.</param>
|
||||||
|
public FollowPointConnection([NotNull] DrawableOsuHitObject start)
|
||||||
|
{
|
||||||
|
Start = start;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
StartTime.BindTo(Start.HitObject.StartTimeBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
bindEvents(Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableOsuHitObject end;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will enter.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public DrawableOsuHitObject End
|
||||||
|
{
|
||||||
|
get => end;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
end = value;
|
||||||
|
|
||||||
|
if (end != null)
|
||||||
|
bindEvents(end);
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
scheduleRefresh();
|
||||||
|
else
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindEvents(DrawableOsuHitObject drawableObject)
|
||||||
|
{
|
||||||
|
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
|
||||||
|
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleRefresh() => Scheduler.AddOnce(refresh);
|
||||||
|
|
||||||
|
private void refresh()
|
||||||
|
{
|
||||||
|
ClearInternal();
|
||||||
|
|
||||||
|
if (End == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OsuHitObject osuStart = Start.HitObject;
|
||||||
|
OsuHitObject osuEnd = End.HitObject;
|
||||||
|
|
||||||
|
if (osuEnd.NewCombo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (osuStart is Spinner || osuEnd is Spinner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector2 startPosition = osuStart.EndPosition;
|
||||||
|
Vector2 endPosition = osuEnd.Position;
|
||||||
|
double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime;
|
||||||
|
double endTime = osuEnd.StartTime;
|
||||||
|
|
||||||
|
Vector2 distanceVector = endPosition - startPosition;
|
||||||
|
int distance = (int)distanceVector.Length;
|
||||||
|
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
||||||
|
double duration = endTime - startTime;
|
||||||
|
|
||||||
|
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
|
||||||
|
{
|
||||||
|
float fraction = (float)d / distance;
|
||||||
|
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
||||||
|
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
||||||
|
double fadeOutTime = startTime + fraction * duration;
|
||||||
|
double fadeInTime = fadeOutTime - preempt;
|
||||||
|
|
||||||
|
FollowPoint fp;
|
||||||
|
|
||||||
|
AddInternal(fp = new FollowPoint
|
||||||
|
{
|
||||||
|
Position = pointStartPosition,
|
||||||
|
Rotation = rotation,
|
||||||
|
Alpha = 0,
|
||||||
|
Scale = new Vector2(1.5f * osuEnd.Scale),
|
||||||
|
});
|
||||||
|
|
||||||
|
using (fp.BeginAbsoluteSequence(fadeInTime))
|
||||||
|
{
|
||||||
|
fp.FadeIn(osuEnd.TimeFadeIn);
|
||||||
|
fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out);
|
||||||
|
fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out);
|
||||||
|
fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.Expire(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +1,110 @@
|
|||||||
// 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 osuTK;
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
public class FollowPointRenderer : ConnectionRenderer<OsuHitObject>
|
/// <summary>
|
||||||
|
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class FollowPointRenderer : CompositeDrawable
|
||||||
{
|
{
|
||||||
private int pointDistance = 32;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines how much space there is between points.
|
/// All the <see cref="FollowPointConnection"/>s contained by this <see cref="FollowPointRenderer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PointDistance
|
internal IReadOnlyList<FollowPointConnection> Connections => connections;
|
||||||
{
|
|
||||||
get => pointDistance;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (pointDistance == value) return;
|
|
||||||
|
|
||||||
pointDistance = value;
|
private readonly List<FollowPointConnection> connections = new List<FollowPointConnection>();
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int preEmpt = 800;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
|
|
||||||
/// </summary>
|
|
||||||
public int PreEmpt
|
|
||||||
{
|
|
||||||
get => preEmpt;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (preEmpt == value) return;
|
|
||||||
|
|
||||||
preEmpt = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<OsuHitObject> hitObjects;
|
|
||||||
|
|
||||||
public override IEnumerable<OsuHitObject> HitObjects
|
|
||||||
{
|
|
||||||
get => hitObjects;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
hitObjects = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
private void update()
|
/// <summary>
|
||||||
|
/// Adds the <see cref="FollowPoint"/>s around a <see cref="DrawableOsuHitObject"/>.
|
||||||
|
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableOsuHitObject"/> to add <see cref="FollowPoint"/>s for.</param>
|
||||||
|
public void AddFollowPoints(DrawableOsuHitObject hitObject)
|
||||||
|
=> addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g))));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the <see cref="FollowPoint"/>s around a <see cref="DrawableOsuHitObject"/>.
|
||||||
|
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableOsuHitObject"/> to remove <see cref="FollowPoint"/>s for.</param>
|
||||||
|
public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="FollowPointConnection"/> to this <see cref="FollowPointRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection">The <see cref="FollowPointConnection"/> to add.</param>
|
||||||
|
/// <returns>The index of <paramref name="connection"/> in <see cref="connections"/>.</returns>
|
||||||
|
private void addConnection(FollowPointConnection connection)
|
||||||
{
|
{
|
||||||
ClearInternal();
|
AddInternal(connection);
|
||||||
|
|
||||||
if (hitObjects == null)
|
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
|
||||||
return;
|
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
|
||||||
|
|
||||||
OsuHitObject prevHitObject = null;
|
if (index < connections.Count - 1)
|
||||||
|
|
||||||
foreach (var currHitObject in hitObjects)
|
|
||||||
{
|
{
|
||||||
if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner))
|
// Update the connection's end point to the next connection's start point
|
||||||
{
|
// h1 -> -> -> h2
|
||||||
Vector2 startPosition = prevHitObject.EndPosition;
|
// connection nextGroup
|
||||||
Vector2 endPosition = currHitObject.Position;
|
|
||||||
double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime;
|
|
||||||
double endTime = currHitObject.StartTime;
|
|
||||||
|
|
||||||
Vector2 distanceVector = endPosition - startPosition;
|
FollowPointConnection nextConnection = connections[index + 1];
|
||||||
int distance = (int)distanceVector.Length;
|
connection.End = nextConnection.Start;
|
||||||
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
|
||||||
double duration = endTime - startTime;
|
|
||||||
|
|
||||||
for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance)
|
|
||||||
{
|
|
||||||
float fraction = (float)d / distance;
|
|
||||||
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
|
||||||
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
|
||||||
double fadeOutTime = startTime + fraction * duration;
|
|
||||||
double fadeInTime = fadeOutTime - PreEmpt;
|
|
||||||
|
|
||||||
FollowPoint fp;
|
|
||||||
|
|
||||||
AddInternal(fp = new FollowPoint
|
|
||||||
{
|
|
||||||
Position = pointStartPosition,
|
|
||||||
Rotation = rotation,
|
|
||||||
Alpha = 0,
|
|
||||||
Scale = new Vector2(1.5f * currHitObject.Scale),
|
|
||||||
});
|
|
||||||
|
|
||||||
using (fp.BeginAbsoluteSequence(fadeInTime))
|
|
||||||
{
|
|
||||||
fp.FadeIn(currHitObject.TimeFadeIn);
|
|
||||||
fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out);
|
|
||||||
|
|
||||||
fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
|
|
||||||
|
|
||||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.Expire(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevHitObject = currHitObject;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The end point may be non-null during re-ordering
|
||||||
|
connection.End = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
// Update the previous connection's end point to the current connection's start point
|
||||||
|
// h1 -> -> -> h2
|
||||||
|
// prevGroup connection
|
||||||
|
|
||||||
|
FollowPointConnection previousConnection = connections[index - 1];
|
||||||
|
previousConnection.End = connection.Start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a <see cref="FollowPointConnection"/> from this <see cref="FollowPointRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection">The <see cref="FollowPointConnection"/> to remove.</param>
|
||||||
|
/// <returns>Whether <paramref name="connection"/> was removed.</returns>
|
||||||
|
private void removeGroup(FollowPointConnection connection)
|
||||||
|
{
|
||||||
|
RemoveInternal(connection);
|
||||||
|
|
||||||
|
int index = connections.IndexOf(connection);
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
// Update the previous connection's end point to the next connection's start point
|
||||||
|
// h1 -> -> -> h2 -> -> -> h3
|
||||||
|
// prevGroup connection nextGroup
|
||||||
|
// The current connection's end point is used since there may not be a next connection
|
||||||
|
FollowPointConnection previousConnection = connections[index - 1];
|
||||||
|
previousConnection.End = connection.End;
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.Remove(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStartTimeChanged(FollowPointConnection connection)
|
||||||
|
{
|
||||||
|
// Naive but can be improved if performance becomes an issue
|
||||||
|
removeGroup(connection);
|
||||||
|
addConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using System.Linq;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
private readonly ApproachCircleProxyContainer approachCircles;
|
private readonly ApproachCircleProxyContainer approachCircles;
|
||||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||||
private readonly ConnectionRenderer<OsuHitObject> connectionLayer;
|
private readonly FollowPointRenderer followPoints;
|
||||||
|
|
||||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
connectionLayer = new FollowPointRenderer
|
followPoints = new FollowPointRenderer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 2,
|
Depth = 2,
|
||||||
@ -64,11 +63,18 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
};
|
};
|
||||||
|
|
||||||
base.Add(h);
|
base.Add(h);
|
||||||
|
|
||||||
|
followPoints.AddFollowPoints((DrawableOsuHitObject)h);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PostProcess()
|
public override bool Remove(DrawableHitObject h)
|
||||||
{
|
{
|
||||||
connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
|
bool result = base.Remove(h);
|
||||||
|
|
||||||
|
if (result)
|
||||||
|
followPoints.RemoveFollowPoints((DrawableOsuHitObject)h);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
@ -32,7 +32,11 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
{
|
{
|
||||||
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
||||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
@ -42,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
},
|
},
|
||||||
new TestDistanceSnapGrid(new HitObject(), grid_position)
|
new TestDistanceSnapGrid(new HitObject(), grid_position)
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
[TestCase(2)]
|
[TestCase(2)]
|
||||||
@ -57,12 +61,29 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
|
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLimitedDistance()
|
||||||
|
{
|
||||||
|
AddStep("create limited grid", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDistanceSnapGrid : DistanceSnapGrid
|
private class TestDistanceSnapGrid : DistanceSnapGrid
|
||||||
{
|
{
|
||||||
public new float DistanceSpacing => base.DistanceSpacing;
|
public new float DistanceSpacing => base.DistanceSpacing;
|
||||||
|
|
||||||
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
|
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
|
||||||
: base(hitObject, centrePosition)
|
: base(hitObject, nextHitObject, centrePosition)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
|
|
||||||
int beatIndex = 0;
|
int beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++)
|
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
@ -90,7 +111,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
|
for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
@ -103,7 +124,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++)
|
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
@ -116,7 +137,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
|
|
||||||
beatIndex = 0;
|
beatIndex = 0;
|
||||||
|
|
||||||
for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
|
for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
|
||||||
{
|
{
|
||||||
AddInternal(new Circle
|
AddInternal(new Circle
|
||||||
{
|
{
|
||||||
@ -138,9 +159,9 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
|
|
||||||
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
public float GetBeatSnapDistanceAt(double referenceTime) => 10;
|
||||||
|
|
||||||
public float DurationToDistance(double referenceTime, double duration) => 0;
|
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
|
||||||
|
|
||||||
public double DistanceToDuration(double referenceTime, float distance) => 0;
|
public double DistanceToDuration(double referenceTime, float distance) => distance;
|
||||||
|
|
||||||
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ 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.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
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;
|
||||||
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StartStopButton : Button
|
private class StartStopButton : OsuButton
|
||||||
{
|
{
|
||||||
private IAdjustableClock adjustableClock;
|
private IAdjustableClock adjustableClock;
|
||||||
private bool started;
|
private bool started;
|
||||||
|
35
osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
Normal file
35
osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneTimingScreen : EditorClockTestScene
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(ControlPointTable),
|
||||||
|
typeof(ControlPointSettings),
|
||||||
|
typeof(Section<>),
|
||||||
|
typeof(TimingSection),
|
||||||
|
typeof(EffectSection),
|
||||||
|
typeof(SampleSection),
|
||||||
|
typeof(DifficultySection),
|
||||||
|
typeof(RowAttribute)
|
||||||
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
Child = new TimingScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -237,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
|
AddUntilStep("player not exited", () => Player.IsCurrentScreen());
|
||||||
AddStep("exit", () => Player.Exit());
|
AddStep("exit", () => Player.Exit());
|
||||||
confirmExited();
|
confirmExited();
|
||||||
|
confirmNoTrackAdjustments();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmPaused()
|
private void confirmPaused()
|
||||||
@ -258,6 +259,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
|
AddUntilStep("player exited", () => !Player.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void confirmNoTrackAdjustments()
|
||||||
|
{
|
||||||
|
AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1);
|
||||||
|
}
|
||||||
|
|
||||||
private void restart() => AddStep("restart", () => Player.Restart());
|
private void restart() => AddStep("restart", () => Player.Restart());
|
||||||
private void pause() => AddStep("pause", () => Player.Pause());
|
private void pause() => AddStep("pause", () => Player.Pause());
|
||||||
private void resume() => AddStep("resume", () => Player.Resume());
|
private void resume() => AddStep("resume", () => Player.Resume());
|
||||||
|
@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Ranking;
|
using osu.Game.Screens.Ranking;
|
||||||
using osu.Game.Screens.Ranking.Pages;
|
using osu.Game.Screens.Ranking.Pages;
|
||||||
@ -27,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
typeof(ScoreResultsPage),
|
typeof(ScoreResultsPage),
|
||||||
typeof(RetryButton),
|
typeof(RetryButton),
|
||||||
typeof(ReplayDownloadButton),
|
typeof(ReplayDownloadButton),
|
||||||
typeof(LocalLeaderboardPage)
|
typeof(LocalLeaderboardPage),
|
||||||
|
typeof(TestPlayer)
|
||||||
};
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -43,26 +49,82 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
|
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
|
||||||
if (beatmapInfo != null)
|
if (beatmapInfo != null)
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
|
}
|
||||||
|
|
||||||
LoadScreen(new SoloResults(new ScoreInfo
|
private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo
|
||||||
|
{
|
||||||
|
TotalScore = 2845370,
|
||||||
|
Accuracy = 0.98,
|
||||||
|
MaxCombo = 123,
|
||||||
|
Rank = ScoreRank.A,
|
||||||
|
Date = DateTimeOffset.Now,
|
||||||
|
Statistics = new Dictionary<HitResult, int>
|
||||||
{
|
{
|
||||||
TotalScore = 2845370,
|
{ HitResult.Great, 50 },
|
||||||
Accuracy = 0.98,
|
{ HitResult.Good, 20 },
|
||||||
MaxCombo = 123,
|
{ HitResult.Meh, 50 },
|
||||||
Rank = ScoreRank.A,
|
{ HitResult.Miss, 1 }
|
||||||
Date = DateTimeOffset.Now,
|
},
|
||||||
Statistics = new Dictionary<HitResult, int>
|
User = new User
|
||||||
|
{
|
||||||
|
Username = "peppy",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResultsWithoutPlayer()
|
||||||
|
{
|
||||||
|
TestSoloResults screen = null;
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResultsWithPlayer()
|
||||||
|
{
|
||||||
|
TestSoloResults screen = null;
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
||||||
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestResultsContainer : Container
|
||||||
|
{
|
||||||
|
[Cached(typeof(Player))]
|
||||||
|
private readonly Player player = new TestPlayer();
|
||||||
|
|
||||||
|
public TestResultsContainer(IScreen screen)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = new OsuScreenStack(screen)
|
||||||
{
|
{
|
||||||
{ HitResult.Great, 50 },
|
RelativeSizeAxes = Axes.Both,
|
||||||
{ HitResult.Good, 20 },
|
};
|
||||||
{ HitResult.Meh, 50 },
|
}
|
||||||
{ HitResult.Miss, 1 }
|
}
|
||||||
},
|
|
||||||
User = new User
|
private class TestSoloResults : SoloResults
|
||||||
{
|
{
|
||||||
Username = "peppy",
|
public HotkeyRetryOverlay RetryOverlay;
|
||||||
}
|
|
||||||
}));
|
public TestSoloResults(ScoreInfo score)
|
||||||
|
: base(score)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,7 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
foreach (var r in ladder.Rounds)
|
foreach (var r in ladder.Rounds)
|
||||||
foreach (var b in r.Beatmaps)
|
foreach (var b in r.Beatmaps)
|
||||||
if (b.BeatmapInfo == null)
|
if (b.BeatmapInfo == null && b.ID > 0)
|
||||||
{
|
{
|
||||||
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
|
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
|
||||||
req.Perform(API);
|
req.Perform(API);
|
||||||
|
@ -13,11 +13,13 @@ namespace osu.Game.Audio
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when this <see cref="PreviewTrack"/> has stopped playing.
|
/// Invoked when this <see cref="PreviewTrack"/> has stopped playing.
|
||||||
|
/// Not invoked in a thread-safe context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action Stopped;
|
public event Action Stopped;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when this <see cref="PreviewTrack"/> has started playing.
|
/// Invoked when this <see cref="PreviewTrack"/> has started playing.
|
||||||
|
/// Not invoked in a thread-safe context.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action Started;
|
public event Action Started;
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ namespace osu.Game.Audio
|
|||||||
{
|
{
|
||||||
track = GetTrack();
|
track = GetTrack();
|
||||||
if (track != null)
|
if (track != null)
|
||||||
track.Completed += () => Schedule(Stop);
|
track.Completed += Stop;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -93,6 +95,7 @@ namespace osu.Game.Audio
|
|||||||
hasStarted = false;
|
hasStarted = false;
|
||||||
|
|
||||||
track.Stop();
|
track.Stop();
|
||||||
|
|
||||||
Stopped?.Invoke();
|
Stopped?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,18 +46,18 @@ namespace osu.Game.Audio
|
|||||||
{
|
{
|
||||||
var track = CreatePreviewTrack(beatmapSetInfo, trackStore);
|
var track = CreatePreviewTrack(beatmapSetInfo, trackStore);
|
||||||
|
|
||||||
track.Started += () =>
|
track.Started += () => Schedule(() =>
|
||||||
{
|
{
|
||||||
current?.Stop();
|
current?.Stop();
|
||||||
current = track;
|
current = track;
|
||||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||||
};
|
});
|
||||||
|
|
||||||
track.Stopped += () =>
|
track.Stopped += () => Schedule(() =>
|
||||||
{
|
{
|
||||||
current = null;
|
current = null;
|
||||||
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
|
||||||
};
|
});
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,11 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
set => BeatLengthBindable.Value = value;
|
set => BeatLengthBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The BPM at this control point.
|
||||||
|
/// </summary>
|
||||||
|
public double BPM => 60000 / BeatLength;
|
||||||
|
|
||||||
public override bool EquivalentTo(ControlPoint other) =>
|
public override bool EquivalentTo(ControlPoint other) =>
|
||||||
other is TimingControlPoint otherTyped
|
other is TimingControlPoint otherTyped
|
||||||
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
|
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
|
||||||
|
@ -8,9 +8,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Overlays.Notifications;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers
|
namespace osu.Game.Graphics.Containers
|
||||||
@ -23,21 +20,12 @@ namespace osu.Game.Graphics.Containers
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OsuGame game;
|
private OsuGame game;
|
||||||
private ChannelManager channelManager;
|
|
||||||
private Action showNotImplementedError;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager)
|
private void load(OsuGame game)
|
||||||
{
|
{
|
||||||
// will be null in tests
|
// will be null in tests
|
||||||
this.game = game;
|
this.game = game;
|
||||||
this.channelManager = channelManager;
|
|
||||||
|
|
||||||
showNotImplementedError = () => notifications?.Post(new SimpleNotification
|
|
||||||
{
|
|
||||||
Text = @"This link type is not yet supported!",
|
|
||||||
Icon = FontAwesome.Solid.LifeRing,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddLinks(string text, List<Link> links)
|
public void AddLinks(string text, List<Link> links)
|
||||||
@ -56,85 +44,47 @@ namespace osu.Game.Graphics.Containers
|
|||||||
foreach (var link in links)
|
foreach (var link in links)
|
||||||
{
|
{
|
||||||
AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd));
|
AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd));
|
||||||
AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument);
|
AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
|
||||||
previousLinkEnd = link.Index + link.Length;
|
previousLinkEnd = link.Index + link.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddText(text.Substring(previousLinkEnd));
|
AddText(text.Substring(previousLinkEnd));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Drawable> AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
public void AddLink(string text, string url, Action<SpriteText> creationParameters = null) =>
|
||||||
=> createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText);
|
createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url);
|
||||||
|
|
||||||
public IEnumerable<Drawable> AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
public void AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
||||||
=> createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action);
|
=> createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action);
|
||||||
|
|
||||||
public IEnumerable<Drawable> AddLink(IEnumerable<SpriteText> text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null)
|
public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
|
||||||
|
=> createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null);
|
||||||
|
|
||||||
|
public void AddLink(IEnumerable<SpriteText> text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null)
|
||||||
{
|
{
|
||||||
foreach (var t in text)
|
foreach (var t in text)
|
||||||
AddArbitraryDrawable(t);
|
AddArbitraryDrawable(t);
|
||||||
|
|
||||||
return createLink(text, null, url, linkType, linkArgument, tooltipText);
|
createLink(text, new LinkDetails(action, linkArgument), tooltipText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Drawable> AddUserLink(User user, Action<SpriteText> creationParameters = null)
|
public void AddUserLink(User user, Action<SpriteText> creationParameters = null)
|
||||||
=> createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile");
|
=> createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile");
|
||||||
|
|
||||||
private IEnumerable<Drawable> createLink(IEnumerable<Drawable> drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null)
|
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
|
||||||
{
|
{
|
||||||
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
|
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
TooltipText = tooltipText ?? (url != text ? url : string.Empty),
|
TooltipText = tooltipText,
|
||||||
Action = action ?? (() =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
switch (linkType)
|
if (action != null)
|
||||||
{
|
action();
|
||||||
case LinkAction.OpenBeatmap:
|
else
|
||||||
// TODO: proper query params handling
|
game.HandleLink(link);
|
||||||
if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId))
|
},
|
||||||
game?.ShowBeatmap(beatmapId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LinkAction.OpenBeatmapSet:
|
|
||||||
if (int.TryParse(linkArgument, out int setId))
|
|
||||||
game?.ShowBeatmapSet(setId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LinkAction.OpenChannel:
|
|
||||||
try
|
|
||||||
{
|
|
||||||
channelManager?.OpenChannel(linkArgument);
|
|
||||||
}
|
|
||||||
catch (ChannelNotFoundException)
|
|
||||||
{
|
|
||||||
Logger.Log($"The requested channel \"{linkArgument}\" does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LinkAction.OpenEditorTimestamp:
|
|
||||||
case LinkAction.JoinMultiplayerMatch:
|
|
||||||
case LinkAction.Spectate:
|
|
||||||
showNotImplementedError?.Invoke();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LinkAction.External:
|
|
||||||
game?.OpenUrlExternally(url);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LinkAction.OpenUserProfile:
|
|
||||||
if (long.TryParse(linkArgument, out long userId))
|
|
||||||
game?.ShowUser(userId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action.");
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return drawables;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
|
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
|
||||||
|
@ -1,10 +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.Diagnostics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -19,53 +21,104 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class OsuButton : Button
|
public class OsuButton : Button
|
||||||
{
|
{
|
||||||
private Box hover;
|
public string Text
|
||||||
|
{
|
||||||
|
get => SpriteText?.Text;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SpriteText != null)
|
||||||
|
SpriteText.Text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4? backgroundColour;
|
||||||
|
|
||||||
|
public Color4 BackgroundColour
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
backgroundColour = value;
|
||||||
|
Background.FadeColour(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
protected Box Hover;
|
||||||
|
protected Box Background;
|
||||||
|
protected SpriteText SpriteText;
|
||||||
|
|
||||||
public OsuButton()
|
public OsuButton()
|
||||||
{
|
{
|
||||||
Height = 40;
|
Height = 40;
|
||||||
|
|
||||||
Content.Masking = true;
|
AddInternal(Content = new Container
|
||||||
Content.CornerRadius = 5;
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Background = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
Hover = new Box
|
||||||
|
{
|
||||||
|
Alpha = 0,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White.Opacity(.1f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Depth = float.MinValue
|
||||||
|
},
|
||||||
|
SpriteText = CreateText(),
|
||||||
|
new HoverClickSounds(HoverSampleSet.Loud),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Enabled.BindValueChanged(enabledChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
BackgroundColour = colours.BlueDark;
|
if (backgroundColour == null)
|
||||||
|
BackgroundColour = colours.BlueDark;
|
||||||
AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
hover = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Colour = Color4.White.Opacity(0.1f),
|
|
||||||
Alpha = 0,
|
|
||||||
Depth = -1
|
|
||||||
},
|
|
||||||
new HoverClickSounds(HoverSampleSet.Loud),
|
|
||||||
});
|
|
||||||
|
|
||||||
Enabled.ValueChanged += enabledChanged;
|
Enabled.ValueChanged += enabledChanged;
|
||||||
Enabled.TriggerChange();
|
Enabled.TriggerChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enabledChanged(ValueChangedEvent<bool> e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
|
if (Enabled.Value)
|
||||||
|
{
|
||||||
|
Debug.Assert(backgroundColour != null);
|
||||||
|
Background.FlashColour(backgroundColour.Value, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
hover.FadeIn(200);
|
if (Enabled.Value)
|
||||||
return true;
|
Hover.FadeIn(200, Easing.OutQuint);
|
||||||
|
|
||||||
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
hover.FadeOut(200);
|
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
|
|
||||||
|
Hover.FadeOut(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
@ -80,12 +133,17 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
return base.OnMouseUp(e);
|
return base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreateText() => new OsuSpriteText
|
protected virtual SpriteText CreateText() => new OsuSpriteText
|
||||||
{
|
{
|
||||||
Depth = -1,
|
Depth = -1,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold)
|
Font = OsuFont.GetFont(weight: FontWeight.Bold)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private void enabledChanged(ValueChangedEvent<bool> e)
|
||||||
|
{
|
||||||
|
this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
// 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.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
|
||||||
|
|
||||||
namespace osu.Game.IO.Archives
|
namespace osu.Game.IO.Archives
|
||||||
{
|
{
|
||||||
public sealed class ZipArchiveReader : ArchiveReader
|
public sealed class ZipArchiveReader : ArchiveReader
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// List of substrings that indicate a file should be ignored during the import process
|
|
||||||
/// (usually due to representing no useful data and being autogenerated by the OS).
|
|
||||||
/// </summary>
|
|
||||||
private static readonly string[] filename_ignore_list =
|
|
||||||
{
|
|
||||||
// Mac-specific
|
|
||||||
"__MACOSX",
|
|
||||||
".DS_Store",
|
|
||||||
// Windows-specific
|
|
||||||
"Thumbs.db"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly Stream archiveStream;
|
private readonly Stream archiveStream;
|
||||||
private readonly ZipArchive archive;
|
private readonly ZipArchive archive;
|
||||||
|
|
||||||
@ -58,9 +44,7 @@ namespace osu.Game.IO.Archives
|
|||||||
archiveStream.Dispose();
|
archiveStream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool canBeIgnored(IEntry entry) => filename_ignore_list.Any(ignoredName => entry.Key.IndexOf(ignoredName, StringComparison.OrdinalIgnoreCase) >= 0);
|
public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames();
|
||||||
|
|
||||||
public override IEnumerable<string> Filenames => archive.Entries.Where(e => !canBeIgnored(e)).Select(e => e.Key).ToArray();
|
|
||||||
|
|
||||||
public override Stream GetUnderlyingStream() => archiveStream;
|
public override Stream GetUnderlyingStream() => archiveStream;
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ namespace osu.Game.Online.Chat
|
|||||||
//since we just changed the line display text, offset any already processed links.
|
//since we just changed the line display text, offset any already processed links.
|
||||||
result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0);
|
result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0);
|
||||||
|
|
||||||
var details = getLinkDetails(linkText);
|
var details = GetLinkDetails(linkText);
|
||||||
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
|
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
|
||||||
|
|
||||||
//adjust the offset for processing the current matches group.
|
//adjust the offset for processing the current matches group.
|
||||||
@ -98,7 +98,7 @@ namespace osu.Game.Online.Chat
|
|||||||
var linkText = m.Groups["link"].Value;
|
var linkText = m.Groups["link"].Value;
|
||||||
var indexLength = linkText.Length;
|
var indexLength = linkText.Length;
|
||||||
|
|
||||||
var details = getLinkDetails(linkText);
|
var details = GetLinkDetails(linkText);
|
||||||
var link = new Link(linkText, index, indexLength, details.Action, details.Argument);
|
var link = new Link(linkText, index, indexLength, details.Action, details.Argument);
|
||||||
|
|
||||||
// sometimes an already-processed formatted link can reduce to a simple URL, too
|
// sometimes an already-processed formatted link can reduce to a simple URL, too
|
||||||
@ -109,7 +109,7 @@ namespace osu.Game.Online.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkDetails getLinkDetails(string url)
|
public static LinkDetails GetLinkDetails(string url)
|
||||||
{
|
{
|
||||||
var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
args[0] = args[0].TrimEnd(':');
|
args[0] = args[0].TrimEnd(':');
|
||||||
@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat
|
|||||||
OriginalText = Text = text;
|
OriginalText = Text = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class LinkDetails
|
public class LinkDetails
|
||||||
|
{
|
||||||
|
public LinkAction Action;
|
||||||
|
public string Argument;
|
||||||
|
|
||||||
|
public LinkDetails(LinkAction action, string argument)
|
||||||
{
|
{
|
||||||
public LinkAction Action;
|
Action = action;
|
||||||
public string Argument;
|
Argument = argument;
|
||||||
|
|
||||||
public LinkDetails(LinkAction action, string argument)
|
|
||||||
{
|
|
||||||
Action = action;
|
|
||||||
Argument = argument;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat
|
|||||||
JoinMultiplayerMatch,
|
JoinMultiplayerMatch,
|
||||||
Spectate,
|
Spectate,
|
||||||
OpenUserProfile,
|
OpenUserProfile,
|
||||||
|
Custom
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Link : IComparable<Link>
|
public class Link : IComparable<Link>
|
||||||
|
@ -21,6 +21,7 @@ using osu.Game.Users.Drawables;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Online.Leaderboards
|
namespace osu.Game.Online.Leaderboards
|
||||||
{
|
{
|
||||||
@ -37,6 +38,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
private readonly int rank;
|
private readonly int rank;
|
||||||
|
private readonly bool allowHighlight;
|
||||||
|
|
||||||
private Box background;
|
private Box background;
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -49,17 +51,18 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
|
|
||||||
private List<ScoreComponentLabel> statisticsLabels;
|
private List<ScoreComponentLabel> statisticsLabels;
|
||||||
|
|
||||||
public LeaderboardScore(ScoreInfo score, int rank)
|
public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true)
|
||||||
{
|
{
|
||||||
this.score = score;
|
this.score = score;
|
||||||
this.rank = rank;
|
this.rank = rank;
|
||||||
|
this.allowHighlight = allowHighlight;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = HEIGHT;
|
Height = HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(IAPIProvider api, OsuColour colour)
|
||||||
{
|
{
|
||||||
var user = score.User;
|
var user = score.User;
|
||||||
|
|
||||||
@ -100,7 +103,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
background = new Box
|
background = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Black,
|
Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black,
|
||||||
Alpha = background_alpha,
|
Alpha = background_alpha,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -215,31 +215,102 @@ namespace osu.Game
|
|||||||
|
|
||||||
private ExternalLinkOpener externalLinkOpener;
|
private ExternalLinkOpener externalLinkOpener;
|
||||||
|
|
||||||
public void OpenUrlExternally(string url)
|
/// <summary>
|
||||||
|
/// Handle an arbitrary URL. Displays via in-game overlays where possible.
|
||||||
|
/// This can be called from a non-thread-safe non-game-loaded state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL to load.</param>
|
||||||
|
public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle a specific <see cref="LinkDetails"/>.
|
||||||
|
/// This can be called from a non-thread-safe non-game-loaded state.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="link">The link to load.</param>
|
||||||
|
public void HandleLink(LinkDetails link) => Schedule(() =>
|
||||||
|
{
|
||||||
|
switch (link.Action)
|
||||||
|
{
|
||||||
|
case LinkAction.OpenBeatmap:
|
||||||
|
// TODO: proper query params handling
|
||||||
|
if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId))
|
||||||
|
ShowBeatmap(beatmapId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkAction.OpenBeatmapSet:
|
||||||
|
if (int.TryParse(link.Argument, out int setId))
|
||||||
|
ShowBeatmapSet(setId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkAction.OpenChannel:
|
||||||
|
ShowChannel(link.Argument);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkAction.OpenEditorTimestamp:
|
||||||
|
case LinkAction.JoinMultiplayerMatch:
|
||||||
|
case LinkAction.Spectate:
|
||||||
|
waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification
|
||||||
|
{
|
||||||
|
Text = @"This link type is not yet supported!",
|
||||||
|
Icon = FontAwesome.Solid.LifeRing,
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkAction.External:
|
||||||
|
OpenUrlExternally(link.Argument);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkAction.OpenUserProfile:
|
||||||
|
if (long.TryParse(link.Argument, out long userId))
|
||||||
|
ShowUser(userId);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ =>
|
||||||
{
|
{
|
||||||
if (url.StartsWith("/"))
|
if (url.StartsWith("/"))
|
||||||
url = $"{API.Endpoint}{url}";
|
url = $"{API.Endpoint}{url}";
|
||||||
|
|
||||||
externalLinkOpener.OpenUrlExternally(url);
|
externalLinkOpener.OpenUrlExternally(url);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open a specific channel in chat.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">The channel to display.</param>
|
||||||
|
public void ShowChannel(string channel) => waitForReady(() => channelManager, _ =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
channelManager.OpenChannel(channel);
|
||||||
|
}
|
||||||
|
catch (ChannelNotFoundException)
|
||||||
|
{
|
||||||
|
Logger.Log($"The requested channel \"{channel}\" does not exist");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a beatmap set as an overlay.
|
/// Show a beatmap set as an overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="setId">The set to display.</param>
|
/// <param name="setId">The set to display.</param>
|
||||||
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId);
|
public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a user's profile as an overlay.
|
/// Show a user's profile as an overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user to display.</param>
|
/// <param name="userId">The user to display.</param>
|
||||||
public void ShowUser(long userId) => userProfile.ShowUser(userId);
|
public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Show a beatmap's set as an overlay, displaying the given beatmap.
|
/// Show a beatmap's set as an overlay, displaying the given beatmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmapId">The beatmap to show.</param>
|
/// <param name="beatmapId">The beatmap to show.</param>
|
||||||
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
|
public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Present a beatmap at song select immediately.
|
/// Present a beatmap at song select immediately.
|
||||||
@ -397,6 +468,23 @@ namespace osu.Game
|
|||||||
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
|
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wait for the game (and target component) to become loaded and then run an action.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="retrieveInstance">A function to retrieve a (potentially not-yet-constructed) target instance.</param>
|
||||||
|
/// <param name="action">The action to perform on the instance when load is confirmed.</param>
|
||||||
|
/// <typeparam name="T">The type of the target instance.</typeparam>
|
||||||
|
private void waitForReady<T>(Func<T> retrieveInstance, Action<T> action)
|
||||||
|
where T : Drawable
|
||||||
|
{
|
||||||
|
var instance = retrieveInstance();
|
||||||
|
|
||||||
|
if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true)
|
||||||
|
Schedule(() => waitForReady(retrieveInstance, action));
|
||||||
|
else
|
||||||
|
action(instance);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
t.Font = fontLarge;
|
t.Font = fontLarge;
|
||||||
t.Colour = entryColour;
|
t.Colour = entryColour;
|
||||||
});
|
});
|
||||||
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External,
|
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
|
||||||
creationParameters: t =>
|
creationParameters: t =>
|
||||||
{
|
{
|
||||||
t.Font = fontLarge;
|
t.Font = fontLarge;
|
||||||
@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
t.Colour = entryColour;
|
t.Colour = entryColour;
|
||||||
});
|
});
|
||||||
else if (entry.GithubUser.GithubUrl != null)
|
else if (entry.GithubUser.GithubUrl != null)
|
||||||
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t =>
|
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
|
||||||
{
|
{
|
||||||
t.Font = fontMedium;
|
t.Font = fontMedium;
|
||||||
t.Colour = entryColour;
|
t.Colour = entryColour;
|
||||||
|
@ -168,12 +168,13 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
progressBar = new ProgressBar
|
progressBar = new HoverableProgressBar
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Height = progress_height,
|
Height = progress_height / 2,
|
||||||
FillColour = colours.Yellow,
|
FillColour = colours.Yellow,
|
||||||
|
BackgroundColour = colours.YellowDarker.Opacity(0.5f),
|
||||||
OnSeek = musicController.SeekTo
|
OnSeek = musicController.SeekTo
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -401,5 +402,20 @@ namespace osu.Game.Overlays
|
|||||||
return base.OnDragEnd(e);
|
return base.OnDragEnd(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class HoverableProgressBar : ProgressBar
|
||||||
|
{
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
this.ResizeHeightTo(progress_height, 500, Easing.OutQuint);
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
this.ResizeHeightTo(progress_height / 2, 500, Easing.OutQuint);
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 System;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -9,21 +8,18 @@ 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.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
{
|
{
|
||||||
public class SidebarButton : Button
|
public class SidebarButton : OsuButton
|
||||||
{
|
{
|
||||||
private readonly SpriteIcon drawableIcon;
|
private readonly SpriteIcon drawableIcon;
|
||||||
private readonly SpriteText headerText;
|
private readonly SpriteText headerText;
|
||||||
private readonly Box selectionIndicator;
|
private readonly Box selectionIndicator;
|
||||||
private readonly Container text;
|
private readonly Container text;
|
||||||
public new Action<SettingsSection> Action;
|
|
||||||
|
|
||||||
private SettingsSection section;
|
private SettingsSection section;
|
||||||
|
|
||||||
@ -62,12 +58,11 @@ namespace osu.Game.Overlays.Settings
|
|||||||
|
|
||||||
public SidebarButton()
|
public SidebarButton()
|
||||||
{
|
{
|
||||||
BackgroundColour = OsuColour.Gray(60);
|
|
||||||
Background.Alpha = 0;
|
|
||||||
|
|
||||||
Height = Sidebar.DEFAULT_WIDTH;
|
Height = Sidebar.DEFAULT_WIDTH;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
BackgroundColour = Color4.Black;
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
text = new Container
|
text = new Container
|
||||||
@ -99,7 +94,6 @@ namespace osu.Game.Overlays.Settings
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Loud),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,23 +102,5 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
selectionIndicator.Colour = colours.Yellow;
|
selectionIndicator.Colour = colours.Yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
|
||||||
{
|
|
||||||
Action?.Invoke(section);
|
|
||||||
return base.OnClick(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
Background.FadeTo(0.4f, 200);
|
|
||||||
return base.OnHover(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
Background.FadeTo(0, 200);
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,9 +123,9 @@ namespace osu.Game.Overlays
|
|||||||
var button = new SidebarButton
|
var button = new SidebarButton
|
||||||
{
|
{
|
||||||
Section = section,
|
Section = section,
|
||||||
Action = s =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
SectionsContainer.ScrollTo(s);
|
SectionsContainer.ScrollTo(section);
|
||||||
Sidebar.State = ExpandedState.Contracted;
|
Sidebar.State = ExpandedState.Contracted;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When not selected, input is only required for the blueprint itself to receive IsHovering
|
||||||
|
protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selects this <see cref="SelectionBlueprint"/>, causing it to become visible.
|
/// Selects this <see cref="SelectionBlueprint"/>, causing it to become visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -254,6 +254,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
Debug.Assert(!clickSelectionBegan);
|
Debug.Assert(!clickSelectionBegan);
|
||||||
|
|
||||||
|
// If a select blueprint is already hovered, disallow changes in selection.
|
||||||
|
// Exception is made when holding control, as deselection should still be allowed.
|
||||||
|
if (!e.CurrentState.Keyboard.ControlPressed &&
|
||||||
|
selectionHandler.SelectedBlueprints.Any(s => s.IsHovered))
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints)
|
foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints)
|
||||||
{
|
{
|
||||||
if (blueprint.IsHovered)
|
if (blueprint.IsHovered)
|
||||||
@ -361,7 +367,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
(Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime);
|
(Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime);
|
||||||
|
|
||||||
// Move the hitobjects
|
// Move the hitobjects
|
||||||
selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)));
|
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))))
|
||||||
|
return true;
|
||||||
|
|
||||||
// Apply the start time at the newly snapped-to position
|
// Apply the start time at the newly snapped-to position
|
||||||
double offset = snappedTime - draggedObject.StartTime;
|
double offset = snappedTime - draggedObject.StartTime;
|
||||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
|
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
|
||||||
{
|
{
|
||||||
protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
|
protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition)
|
||||||
: base(hitObject, centrePosition)
|
: base(hitObject, nextHitObject, centrePosition)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X);
|
float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X);
|
||||||
float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y);
|
float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y);
|
||||||
float maxDistance = new Vector2(dx, dy).Length;
|
float maxDistance = new Vector2(dx, dy).Length;
|
||||||
int requiredCircles = (int)(maxDistance / DistanceSpacing);
|
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing));
|
||||||
|
|
||||||
for (int i = 0; i < requiredCircles; i++)
|
for (int i = 0; i < requiredCircles; i++)
|
||||||
{
|
{
|
||||||
@ -65,15 +65,17 @@ 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)
|
||||||
{
|
{
|
||||||
Vector2 direction = position - CentrePosition;
|
if (MaxIntervals == 0)
|
||||||
|
return (CentrePosition, StartTime);
|
||||||
|
|
||||||
|
Vector2 direction = position - CentrePosition;
|
||||||
if (direction == Vector2.Zero)
|
if (direction == Vector2.Zero)
|
||||||
direction = new Vector2(0.001f, 0.001f);
|
direction = new Vector2(0.001f, 0.001f);
|
||||||
|
|
||||||
float distance = direction.Length;
|
float distance = direction.Length;
|
||||||
|
|
||||||
float radius = DistanceSpacing;
|
float radius = DistanceSpacing;
|
||||||
int radialCount = Math.Max(1, (int)Math.Round(distance / radius));
|
int radialCount = MathHelper.Clamp((int)Math.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 = CentrePosition + normalisedDirection * radialCount * radius;
|
||||||
|
@ -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.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -29,6 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected double StartTime { get; private set; }
|
protected double StartTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum number of distance snapping intervals allowed.
|
||||||
|
/// </summary>
|
||||||
|
protected int MaxIntervals { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position which the grid is centred on.
|
/// The position which the grid is centred on.
|
||||||
/// 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="CentrePosition"/> + <see cref="DistanceSpacing"/> in the desired direction.
|
||||||
@ -49,12 +55,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private readonly Cached gridCache = new Cached();
|
private readonly Cached gridCache = new Cached();
|
||||||
private readonly HitObject hitObject;
|
private readonly HitObject hitObject;
|
||||||
|
private readonly HitObject nextHitObject;
|
||||||
|
|
||||||
protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
|
protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition)
|
||||||
{
|
{
|
||||||
this.hitObject = hitObject;
|
this.hitObject = hitObject;
|
||||||
|
this.nextHitObject = nextHitObject;
|
||||||
|
|
||||||
CentrePosition = centrePosition;
|
CentrePosition = centrePosition;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +83,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private void updateSpacing()
|
private void updateSpacing()
|
||||||
{
|
{
|
||||||
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
|
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
|
||||||
|
|
||||||
|
if (nextHitObject == null)
|
||||||
|
MaxIntervals = int.MaxValue;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// +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;
|
||||||
|
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
gridCache.Invalidate();
|
gridCache.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@ 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;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.States;
|
using osu.Framework.Input.States;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Compose.Components
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
/// A component which outlines <see cref="DrawableHitObject"/>s and handles movement of selections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SelectionHandler : CompositeDrawable
|
public class SelectionHandler : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||||
{
|
{
|
||||||
public const float BORDER_RADIUS = 2;
|
public const float BORDER_RADIUS = 2;
|
||||||
|
|
||||||
@ -68,26 +68,24 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// Handles the selected <see cref="DrawableHitObject"/>s being moved.
|
/// Handles the selected <see cref="DrawableHitObject"/>s being moved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="moveEvent">The move event.</param>
|
/// <param name="moveEvent">The move event.</param>
|
||||||
public virtual void HandleMovement(MoveSelectionEvent moveEvent)
|
/// <returns>Whether any <see cref="DrawableHitObject"/>s were moved.</returns>
|
||||||
{
|
public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false;
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
public bool OnPressed(PlatformAction action)
|
||||||
{
|
{
|
||||||
if (e.Repeat)
|
switch (action.ActionMethod)
|
||||||
return base.OnKeyDown(e);
|
|
||||||
|
|
||||||
switch (e.Key)
|
|
||||||
{
|
{
|
||||||
case Key.Delete:
|
case PlatformActionMethod.Delete:
|
||||||
foreach (var h in selectedBlueprints.ToList())
|
foreach (var h in selectedBlueprints.ToList())
|
||||||
placementHandler.Delete(h.DrawableObject.HitObject);
|
placementHandler.Delete(h.DrawableObject.HitObject);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Selection Handling
|
#region Selection Handling
|
||||||
|
50
osu.Game/Screens/Edit/Timing/ControlPointSettings.cs
Normal file
50
osu.Game/Screens/Edit/Timing/ControlPointSettings.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
public class ControlPointSettings : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray3,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = createSections()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||||
|
{
|
||||||
|
new TimingSection(),
|
||||||
|
new DifficultySection(),
|
||||||
|
new SampleSection(),
|
||||||
|
new EffectSection(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
247
osu.Game/Screens/Edit/Timing/ControlPointTable.cs
Normal file
247
osu.Game/Screens/Edit/Timing/ControlPointTable.cs
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
public class ControlPointTable : TableContainer
|
||||||
|
{
|
||||||
|
private const float horizontal_inset = 20;
|
||||||
|
private const float row_height = 25;
|
||||||
|
private const int text_size = 14;
|
||||||
|
|
||||||
|
private readonly FillFlowContainer backgroundFlow;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||||
|
|
||||||
|
public ControlPointTable()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Padding = new MarginPadding { Horizontal = horizontal_inset };
|
||||||
|
RowSize = new Dimension(GridSizeMode.Absolute, row_height);
|
||||||
|
|
||||||
|
AddInternal(backgroundFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = 1f,
|
||||||
|
Padding = new MarginPadding { Horizontal = -horizontal_inset },
|
||||||
|
Margin = new MarginPadding { Top = row_height }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ControlPointGroup> ControlGroups
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Content = null;
|
||||||
|
backgroundFlow.Clear();
|
||||||
|
|
||||||
|
if (value?.Any() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var group in value)
|
||||||
|
{
|
||||||
|
backgroundFlow.Add(new RowBackground(group));
|
||||||
|
}
|
||||||
|
|
||||||
|
Columns = createHeaders();
|
||||||
|
Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableColumn[] createHeaders()
|
||||||
|
{
|
||||||
|
var columns = new List<TableColumn>
|
||||||
|
{
|
||||||
|
new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
|
||||||
|
new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)),
|
||||||
|
new TableColumn("Attributes", Anchor.Centre),
|
||||||
|
};
|
||||||
|
|
||||||
|
return columns.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = $"#{index + 1}",
|
||||||
|
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold),
|
||||||
|
Margin = new MarginPadding(10)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = $"{group.Time:n0}ms",
|
||||||
|
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
|
||||||
|
},
|
||||||
|
new ControlGroupAttributes(group),
|
||||||
|
};
|
||||||
|
|
||||||
|
private class ControlGroupAttributes : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly IBindableList<ControlPoint> controlPoints;
|
||||||
|
|
||||||
|
private readonly FillFlowContainer fill;
|
||||||
|
|
||||||
|
public ControlGroupAttributes(ControlPointGroup group)
|
||||||
|
{
|
||||||
|
InternalChild = fill = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
Spacing = new Vector2(2)
|
||||||
|
};
|
||||||
|
|
||||||
|
controlPoints = group.ControlPoints.GetBoundCopy();
|
||||||
|
controlPoints.ItemsAdded += _ => createChildren();
|
||||||
|
controlPoints.ItemsRemoved += _ => createChildren();
|
||||||
|
|
||||||
|
createChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createChildren()
|
||||||
|
{
|
||||||
|
fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable createAttribute(ControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case TimingControlPoint timing:
|
||||||
|
return new RowAttribute("timing", () => $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}");
|
||||||
|
|
||||||
|
case DifficultyControlPoint difficulty:
|
||||||
|
|
||||||
|
return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x");
|
||||||
|
|
||||||
|
case EffectControlPoint effect:
|
||||||
|
return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}");
|
||||||
|
|
||||||
|
case SampleControlPoint sample:
|
||||||
|
return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty);
|
||||||
|
|
||||||
|
private class HeaderText : OsuSpriteText
|
||||||
|
{
|
||||||
|
public HeaderText(string text)
|
||||||
|
{
|
||||||
|
Text = text.ToUpper();
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RowBackground : OsuClickableContainer
|
||||||
|
{
|
||||||
|
private readonly ControlPointGroup controlGroup;
|
||||||
|
private const int fade_duration = 100;
|
||||||
|
|
||||||
|
private readonly Box hoveredBackground;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||||
|
|
||||||
|
public RowBackground(ControlPointGroup controlGroup)
|
||||||
|
{
|
||||||
|
this.controlGroup = controlGroup;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 25;
|
||||||
|
|
||||||
|
AlwaysPresent = true;
|
||||||
|
|
||||||
|
CornerRadius = 3;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hoveredBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Action = () => selectedGroup.Value = controlGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 colourHover;
|
||||||
|
private Color4 colourSelected;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
hoveredBackground.Colour = colourHover = colours.BlueDarker;
|
||||||
|
colourSelected = colours.YellowDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool selected;
|
||||||
|
|
||||||
|
protected bool Selected
|
||||||
|
{
|
||||||
|
get => selected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == selected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
selected = value;
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (selected || IsHovered)
|
||||||
|
hoveredBackground.FadeIn(fade_duration, Easing.OutQuint);
|
||||||
|
else
|
||||||
|
hoveredBackground.FadeOut(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
osu.Game/Screens/Edit/Timing/DifficultySection.cs
Normal file
48
osu.Game/Screens/Edit/Timing/DifficultySection.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class DifficultySection : Section<DifficultyControlPoint>
|
||||||
|
{
|
||||||
|
private SettingsSlider<double> multiplier;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AddRange(new[]
|
||||||
|
{
|
||||||
|
multiplier = new SettingsSlider<double>
|
||||||
|
{
|
||||||
|
LabelText = "Speed Multiplier",
|
||||||
|
Bindable = new DifficultyControlPoint().SpeedMultiplierBindable,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnControlPointChanged(ValueChangedEvent<DifficultyControlPoint> point)
|
||||||
|
{
|
||||||
|
if (point.NewValue != null)
|
||||||
|
{
|
||||||
|
multiplier.Bindable = point.NewValue.SpeedMultiplierBindable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DifficultyControlPoint CreatePoint()
|
||||||
|
{
|
||||||
|
var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
|
return new DifficultyControlPoint
|
||||||
|
{
|
||||||
|
SpeedMultiplier = reference.SpeedMultiplier,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
osu.Game/Screens/Edit/Timing/EffectSection.cs
Normal file
46
osu.Game/Screens/Edit/Timing/EffectSection.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class EffectSection : Section<EffectControlPoint>
|
||||||
|
{
|
||||||
|
private LabelledSwitchButton kiai;
|
||||||
|
private LabelledSwitchButton omitBarLine;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AddRange(new[]
|
||||||
|
{
|
||||||
|
kiai = new LabelledSwitchButton { Label = "Kiai Time" },
|
||||||
|
omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnControlPointChanged(ValueChangedEvent<EffectControlPoint> point)
|
||||||
|
{
|
||||||
|
if (point.NewValue != null)
|
||||||
|
{
|
||||||
|
kiai.Current = point.NewValue.KiaiModeBindable;
|
||||||
|
omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override EffectControlPoint CreatePoint()
|
||||||
|
{
|
||||||
|
var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
|
return new EffectControlPoint
|
||||||
|
{
|
||||||
|
KiaiMode = reference.KiaiMode,
|
||||||
|
OmitFirstBarLine = reference.OmitFirstBarLine
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
osu.Game/Screens/Edit/Timing/RowAttribute.cs
Normal file
60
osu.Game/Screens/Edit/Timing/RowAttribute.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
public class RowAttribute : CompositeDrawable, IHasTooltip
|
||||||
|
{
|
||||||
|
private readonly string header;
|
||||||
|
private readonly Func<string> content;
|
||||||
|
|
||||||
|
public RowAttribute(string header, Func<string> content)
|
||||||
|
{
|
||||||
|
this.header = header;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
Height = 20;
|
||||||
|
|
||||||
|
Anchor = Anchor.CentreLeft;
|
||||||
|
Origin = Anchor.CentreLeft;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Yellow,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(2),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12),
|
||||||
|
Text = header,
|
||||||
|
Colour = colours.Gray3
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TooltipText => content();
|
||||||
|
}
|
||||||
|
}
|
55
osu.Game/Screens/Edit/Timing/SampleSection.cs
Normal file
55
osu.Game/Screens/Edit/Timing/SampleSection.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class SampleSection : Section<SampleControlPoint>
|
||||||
|
{
|
||||||
|
private LabelledTextBox bank;
|
||||||
|
private SettingsSlider<int> volume;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
bank = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Bank Name",
|
||||||
|
},
|
||||||
|
volume = new SettingsSlider<int>
|
||||||
|
{
|
||||||
|
Bindable = new SampleControlPoint().SampleVolumeBindable,
|
||||||
|
LabelText = "Volume",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnControlPointChanged(ValueChangedEvent<SampleControlPoint> point)
|
||||||
|
{
|
||||||
|
if (point.NewValue != null)
|
||||||
|
{
|
||||||
|
bank.Current = point.NewValue.SampleBankBindable;
|
||||||
|
volume.Bindable = point.NewValue.SampleVolumeBindable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SampleControlPoint CreatePoint()
|
||||||
|
{
|
||||||
|
var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
|
return new SampleControlPoint
|
||||||
|
{
|
||||||
|
SampleBank = reference.SampleBank,
|
||||||
|
SampleVolume = reference.SampleVolume,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
osu.Game/Screens/Edit/Timing/Section.cs
Normal file
130
osu.Game/Screens/Edit/Timing/Section.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal abstract class Section<T> : CompositeDrawable
|
||||||
|
where T : ControlPoint
|
||||||
|
{
|
||||||
|
private OsuCheckbox checkbox;
|
||||||
|
private Container content;
|
||||||
|
|
||||||
|
protected FillFlowContainer Flow { get; private set; }
|
||||||
|
|
||||||
|
protected Bindable<T> ControlPoint { get; } = new Bindable<T>();
|
||||||
|
|
||||||
|
private const float header_height = 20;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected Bindable<ControlPointGroup> SelectedGroup { get; private set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeDuration = 200;
|
||||||
|
AutoSizeEasing = Easing.OutQuint;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray1,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = header_height,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
checkbox = new OsuCheckbox
|
||||||
|
{
|
||||||
|
LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = new Container
|
||||||
|
{
|
||||||
|
Y = header_height,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray2,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
Flow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
checkbox.Current.BindValueChanged(selected =>
|
||||||
|
{
|
||||||
|
if (selected.NewValue)
|
||||||
|
{
|
||||||
|
if (SelectedGroup.Value == null)
|
||||||
|
{
|
||||||
|
checkbox.Current.Value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ControlPoint.Value == null)
|
||||||
|
SelectedGroup.Value.Add(ControlPoint.Value = CreatePoint());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ControlPoint.Value != null)
|
||||||
|
{
|
||||||
|
SelectedGroup.Value.Remove(ControlPoint.Value);
|
||||||
|
ControlPoint.Value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
SelectedGroup.BindValueChanged(points =>
|
||||||
|
{
|
||||||
|
ControlPoint.Value = points.NewValue?.ControlPoints.OfType<T>().FirstOrDefault();
|
||||||
|
checkbox.Current.Value = ControlPoint.Value != null;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
ControlPoint.BindValueChanged(OnControlPointChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void OnControlPointChanged(ValueChangedEvent<T> point);
|
||||||
|
|
||||||
|
protected abstract T CreatePoint();
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,151 @@
|
|||||||
// 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.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
public class TimingScreen : EditorScreen
|
public class TimingScreen : EditorScreenWithTimeline
|
||||||
{
|
{
|
||||||
public TimingScreen()
|
[Cached]
|
||||||
|
private Bindable<ControlPointGroup> selectedGroup = new Bindable<ControlPointGroup>();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAdjustableClock clock { get; set; }
|
||||||
|
|
||||||
|
protected override Drawable CreateMainContent() => new GridContainer
|
||||||
{
|
{
|
||||||
Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode");
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new ControlPointList(),
|
||||||
|
new ControlPointSettings(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedGroup.BindValueChanged(selected =>
|
||||||
|
{
|
||||||
|
if (selected.NewValue != null)
|
||||||
|
clock.Seek(selected.NewValue.Time);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ControlPointList : CompositeDrawable
|
||||||
|
{
|
||||||
|
private OsuButton deleteButton;
|
||||||
|
private ControlPointTable table;
|
||||||
|
|
||||||
|
private IBindableList<ControlPointGroup> controlGroups;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IFrameBasedClock clock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = table = new ControlPointTable(),
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Margin = new MarginPadding(10),
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
deleteButton = new OsuButton
|
||||||
|
{
|
||||||
|
Text = "-",
|
||||||
|
Size = new Vector2(30, 30),
|
||||||
|
Action = delete,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
new OsuButton
|
||||||
|
{
|
||||||
|
Text = "+",
|
||||||
|
Action = addNew,
|
||||||
|
Size = new Vector2(30, 30),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
|
||||||
|
|
||||||
|
controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy();
|
||||||
|
controlGroups.ItemsAdded += _ => createContent();
|
||||||
|
controlGroups.ItemsRemoved += _ => createContent();
|
||||||
|
createContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createContent() => table.ControlGroups = controlGroups;
|
||||||
|
|
||||||
|
private void delete()
|
||||||
|
{
|
||||||
|
if (selectedGroup.Value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||||
|
|
||||||
|
selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addNew()
|
||||||
|
{
|
||||||
|
selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
85
osu.Game/Screens/Edit/Timing/TimingSection.cs
Normal file
85
osu.Game/Screens/Edit/Timing/TimingSection.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
internal class TimingSection : Section<TimingControlPoint>
|
||||||
|
{
|
||||||
|
private SettingsSlider<double> bpm;
|
||||||
|
private SettingsEnumDropdown<TimeSignatures> timeSignature;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Flow.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
bpm = new BPMSlider
|
||||||
|
{
|
||||||
|
Bindable = new TimingControlPoint().BeatLengthBindable,
|
||||||
|
LabelText = "BPM",
|
||||||
|
},
|
||||||
|
timeSignature = new SettingsEnumDropdown<TimeSignatures>
|
||||||
|
{
|
||||||
|
LabelText = "Time Signature"
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnControlPointChanged(ValueChangedEvent<TimingControlPoint> point)
|
||||||
|
{
|
||||||
|
if (point.NewValue != null)
|
||||||
|
{
|
||||||
|
bpm.Bindable = point.NewValue.BeatLengthBindable;
|
||||||
|
timeSignature.Bindable = point.NewValue.TimeSignatureBindable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TimingControlPoint CreatePoint()
|
||||||
|
{
|
||||||
|
var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time);
|
||||||
|
|
||||||
|
return new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLength = reference.BeatLength,
|
||||||
|
TimeSignature = reference.TimeSignature
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BPMSlider : SettingsSlider<double>
|
||||||
|
{
|
||||||
|
private readonly BindableDouble beatLengthBindable = new BindableDouble();
|
||||||
|
|
||||||
|
private BindableDouble bpmBindable;
|
||||||
|
|
||||||
|
public override Bindable<double> Bindable
|
||||||
|
{
|
||||||
|
get => base.Bindable;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
// incoming will be beatlength
|
||||||
|
|
||||||
|
beatLengthBindable.UnbindBindings();
|
||||||
|
beatLengthBindable.BindTo(value);
|
||||||
|
|
||||||
|
base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value))
|
||||||
|
{
|
||||||
|
MinValue = beatLengthToBpm(beatLengthBindable.MaxValue),
|
||||||
|
MaxValue = beatLengthToBpm(beatLengthBindable.MinValue),
|
||||||
|
Default = beatLengthToBpm(beatLengthBindable.Default),
|
||||||
|
};
|
||||||
|
|
||||||
|
bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double beatLengthToBpm(double beatLength) => 60000 / beatLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components
|
|||||||
Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)),
|
Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)),
|
||||||
Font = OsuFont.GetFont(size: TextSize),
|
Font = OsuFont.GetFont(size: TextSize),
|
||||||
}
|
}
|
||||||
}, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap");
|
}, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,16 +162,12 @@ namespace osu.Game.Screens.Play
|
|||||||
if (sourceClock != beatmap.Track)
|
if (sourceClock != beatmap.Track)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
removeSourceClockAdjustments();
|
||||||
|
|
||||||
sourceClock = new TrackVirtual(beatmap.Track.Length);
|
sourceClock = new TrackVirtual(beatmap.Track.Length);
|
||||||
adjustableClock.ChangeSource(sourceClock);
|
adjustableClock.ChangeSource(sourceClock);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetLocalAdjustments()
|
|
||||||
{
|
|
||||||
// In the case of replays, we may have changed the playback rate.
|
|
||||||
UserPlaybackRate.Value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
if (!IsPaused.Value)
|
if (!IsPaused.Value)
|
||||||
@ -198,6 +194,14 @@ namespace osu.Game.Screens.Play
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
removeSourceClockAdjustments();
|
||||||
|
sourceClock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSourceClockAdjustments()
|
||||||
|
{
|
||||||
|
sourceClock.ResetSpeedAdjustments();
|
||||||
(sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
(sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -536,8 +536,6 @@ namespace osu.Game.Screens.Play
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameplayClockContainer.ResetLocalAdjustments();
|
|
||||||
|
|
||||||
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
// GameplayClockContainer performs seeks / start / stop operations on the beatmap's track.
|
||||||
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
// as we are no longer the current screen, we cannot guarantee the track is still usable.
|
||||||
GameplayClockContainer.StopUsingBeatmapClock();
|
GameplayClockContainer.StopUsingBeatmapClock();
|
||||||
|
@ -116,146 +116,147 @@ namespace osu.Game.Screens.Ranking
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChild = new AspectContainer
|
||||||
{
|
{
|
||||||
new AspectContainer
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Height = overscan,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Y,
|
circleOuterBackground = new CircularContainer
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Height = overscan,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
circleOuterBackground = new CircularContainer
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
new Box
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new Box
|
Alpha = 0.2f,
|
||||||
{
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.2f,
|
Colour = Color4.Black,
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
circleOuter = new CircularContainer
|
|
||||||
{
|
|
||||||
Size = new Vector2(circle_outer_scale),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Colour = Color4.Black.Opacity(0.4f),
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Radius = 15,
|
|
||||||
},
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.White,
|
|
||||||
},
|
|
||||||
backgroundParallax = new ParallaxContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ParallaxAmount = 0.01f,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Sprite
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0.2f,
|
|
||||||
Texture = Beatmap.Value.Background,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
FillMode = FillMode.Fill
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modeChangeButtons = new ResultModeTabControl
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 50,
|
|
||||||
Margin = new MarginPadding { Bottom = 110 },
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Text = $"{Score.MaxCombo}x",
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
|
||||||
X = 0.1f,
|
|
||||||
Colour = colours.BlueDarker,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = "max combo",
|
|
||||||
Font = OsuFont.GetFont(size: 20),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.1f,
|
|
||||||
Colour = colours.Gray6,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Text = $"{Score.Accuracy:P2}",
|
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.9f,
|
|
||||||
Colour = colours.BlueDarker,
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = "accuracy",
|
|
||||||
Font = OsuFont.GetFont(size: 20),
|
|
||||||
RelativePositionAxes = Axes.X,
|
|
||||||
X = 0.9f,
|
|
||||||
Colour = colours.Gray6,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
circleInner = new CircularContainer
|
|
||||||
{
|
|
||||||
Size = new Vector2(0.6f),
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Colour = Color4.Black.Opacity(0.4f),
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
Radius = 15,
|
|
||||||
},
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Masking = true,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.White,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
circleOuter = new CircularContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(circle_outer_scale),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = Color4.Black.Opacity(0.4f),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 15,
|
||||||
|
},
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White,
|
||||||
|
},
|
||||||
|
backgroundParallax = new ParallaxContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ParallaxAmount = 0.01f,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
Texture = Beatmap.Value.Background,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
FillMode = FillMode.Fill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modeChangeButtons = new ResultModeTabControl
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 50,
|
||||||
|
Margin = new MarginPadding { Bottom = 110 },
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Text = $"{Score.MaxCombo}x",
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
||||||
|
X = 0.1f,
|
||||||
|
Colour = colours.BlueDarker,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "max combo",
|
||||||
|
Font = OsuFont.GetFont(size: 20),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.1f,
|
||||||
|
Colour = colours.Gray6,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Text = $"{Score.Accuracy:P2}",
|
||||||
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.9f,
|
||||||
|
Colour = colours.BlueDarker,
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = "accuracy",
|
||||||
|
Font = OsuFont.GetFont(size: 20),
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
X = 0.9f,
|
||||||
|
Colour = colours.Gray6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
circleInner = new CircularContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(0.6f),
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Colour = Color4.Black.Opacity(0.4f),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
Radius = 15,
|
||||||
|
},
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
new HotkeyRetryOverlay
|
};
|
||||||
|
|
||||||
|
if (player != null)
|
||||||
|
{
|
||||||
|
AddInternal(new HotkeyRetryOverlay
|
||||||
{
|
{
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
@ -263,8 +264,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
|
|
||||||
player?.Restart();
|
player?.Restart();
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
var pages = CreateResultPages();
|
var pages = CreateResultPages();
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index)
|
protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope)
|
||||||
{
|
{
|
||||||
Action = () => ScoreSelected?.Invoke(model)
|
Action = () => ScoreSelected?.Invoke(model)
|
||||||
};
|
};
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
if (newScore == null)
|
if (newScore == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position)
|
LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false)
|
||||||
{
|
{
|
||||||
Action = () => ScoreSelected?.Invoke(newScore.Score)
|
Action = () => ScoreSelected?.Invoke(newScore.Score)
|
||||||
}, drawableScore =>
|
}, drawableScore =>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
<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" Version="2019.1029.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2019.1106.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
@ -118,8 +118,8 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||||
<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" Version="2019.1029.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2019.1106.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1029.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1106.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.11.0" />
|
<PackageReference Include="NUnit" Version="3.11.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Foundation;
|
using Foundation;
|
||||||
using osu.Framework.iOS;
|
using osu.Framework.iOS;
|
||||||
using osu.Game;
|
using osu.Framework.Threading;
|
||||||
using UIKit;
|
using UIKit;
|
||||||
|
|
||||||
namespace osu.iOS
|
namespace osu.iOS
|
||||||
@ -16,9 +16,12 @@ namespace osu.iOS
|
|||||||
|
|
||||||
protected override Framework.Game CreateGame() => game = new OsuGameIOS();
|
protected override Framework.Game CreateGame() => game = new OsuGameIOS();
|
||||||
|
|
||||||
public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
|
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
|
||||||
{
|
{
|
||||||
Task.Run(() => game.Import(url.Path));
|
if (url.IsFileUrl)
|
||||||
|
Task.Run(() => game.Import(url.Path));
|
||||||
|
else
|
||||||
|
Task.Run(() => game.HandleLink(url.AbsoluteString));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
<string>0.1.0</string>
|
<string>0.1.0</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>10.0</string>
|
<string>10.0</string>
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
@ -32,9 +34,9 @@
|
|||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>We don't really use the camera.</string>
|
<string>We don't really use the camera.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>We don't really use the microphone.</string>
|
<string>We don't really use the microphone.</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
@ -109,5 +111,17 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>osu</string>
|
||||||
|
<string>osump</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
Loading…
Reference in New Issue
Block a user