mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge branch 'gcc-abstraction' into multiplayer-spectator-screen
This commit is contained in:
@ -2,13 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||||
{
|
{
|
||||||
@ -21,26 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
protected Vector2 Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var frame = CurrentFrame;
|
|
||||||
|
|
||||||
if (frame == null)
|
|
||||||
return Vector2.Zero;
|
|
||||||
|
|
||||||
Debug.Assert(CurrentTime != null);
|
|
||||||
|
|
||||||
return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
inputs.Add(new MousePositionAbsoluteInput
|
inputs.Add(new MousePositionAbsoluteInput
|
||||||
{
|
{
|
||||||
Position = GamefieldToScreenSpace(Position),
|
Position = GamefieldToScreenSpace(position),
|
||||||
});
|
});
|
||||||
inputs.Add(new ReplayState<EmptyFreeformAction>
|
inputs.Add(new ReplayState<EmptyFreeformAction>
|
||||||
{
|
{
|
||||||
|
@ -2,12 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||||
{
|
{
|
||||||
@ -20,26 +18,13 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
protected override bool IsImportant(PippidonReplayFrame frame) => true;
|
||||||
|
|
||||||
protected Vector2 Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var frame = CurrentFrame;
|
|
||||||
|
|
||||||
if (frame == null)
|
|
||||||
return Vector2.Zero;
|
|
||||||
|
|
||||||
Debug.Assert(CurrentTime != null);
|
|
||||||
|
|
||||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
inputs.Add(new MousePositionAbsoluteInput
|
inputs.Add(new MousePositionAbsoluteInput
|
||||||
{
|
{
|
||||||
Position = GamefieldToScreenSpace(Position)
|
Position = GamefieldToScreenSpace(position)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.415.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.416.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -125,10 +125,6 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
private void addFrame(double time, float? position = null, bool dashing = false)
|
private void addFrame(double time, float? position = null, bool dashing = false)
|
||||||
{
|
{
|
||||||
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
|
||||||
if (Replay.Frames.Count == 0)
|
|
||||||
Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
|
|
||||||
|
|
||||||
var last = currentFrame;
|
var last = currentFrame;
|
||||||
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
currentFrame = new CatchReplayFrame(time, position, dashing, last);
|
||||||
Replay.Frames.Add(currentFrame);
|
Replay.Frames.Add(currentFrame);
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -20,29 +19,14 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
protected float? Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var frame = CurrentFrame;
|
|
||||||
|
|
||||||
if (frame == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Debug.Assert(CurrentTime != null);
|
|
||||||
|
|
||||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
if (!Position.HasValue) return;
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
inputs.Add(new CatchReplayState
|
inputs.Add(new CatchReplayState
|
||||||
{
|
{
|
||||||
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
|
PressedActions = CurrentFrame?.Actions ?? new List<CatchAction>(),
|
||||||
CatcherX = Position.Value
|
CatcherX = position
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of frames which are generated at the start of a replay regardless of hitobject content.
|
/// The number of frames which are generated at the start of a replay regardless of hitobject content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int frame_offset = 1;
|
private const int frame_offset = 0;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSingleNote()
|
public void TestSingleNote()
|
||||||
@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||||
@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
|
||||||
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||||
@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||||
|
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||||
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
||||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
||||||
@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
||||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
||||||
@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||||
|
|
||||||
Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
|
Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames");
|
||||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
||||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
||||||
|
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (PlacementActive)
|
if (PlacementActive == PlacementState.Active)
|
||||||
{
|
{
|
||||||
if (result.Time is double endTime)
|
if (result.Time is double endTime)
|
||||||
{
|
{
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (!PlacementActive)
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
Column = result.Playfield as Column;
|
Column = result.Playfield as Column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Replays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
|
|
||||||
if (Replay.Frames.Count == 0)
|
|
||||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
|
|
||||||
|
|
||||||
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneSliderLengthValidity : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
private OsuPlayfield playfield;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||||
|
AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDraggingStartingPointRemainsValid()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
AddStep("Add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(100, 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(300));
|
||||||
|
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
double distanceBefore = 0;
|
||||||
|
|
||||||
|
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(300, 300));
|
||||||
|
|
||||||
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
moveMouse(new Vector2(350, 300));
|
||||||
|
moveMouse(new Vector2(400, 300));
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||||
|
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDraggingEndingPointRemainsValid()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
AddStep("Add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(100, 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(300));
|
||||||
|
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
double distanceBefore = 0;
|
||||||
|
|
||||||
|
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(400, 300));
|
||||||
|
|
||||||
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
moveMouse(new Vector2(350, 300));
|
||||||
|
moveMouse(new Vector2(300, 300));
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||||
|
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a control point is deleted which results in the slider becoming so short it can't exist,
|
||||||
|
/// for simplicity delete the slider rather than having it in an invalid state.
|
||||||
|
///
|
||||||
|
/// Eventually we may need to change this, based on user feedback. I think it's likely enough of
|
||||||
|
/// an edge case that we won't get many complaints, though (and there's always the undo button).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestDeletingPointCausesSliderDeletion()
|
||||||
|
{
|
||||||
|
AddStep("Add slider", () =>
|
||||||
|
{
|
||||||
|
Slider slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(100, 0)),
|
||||||
|
new PathControlPoint(new Vector2(0, 10))
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
moveMouse(new Vector2(400, 300));
|
||||||
|
AddStep("delete second point", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ShiftLeft);
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object deleted", () => EditorBeatmap.HitObjects.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If a scale operation is performed where a single slider is the only thing selected, the path's shape will change.
|
||||||
|
/// If the scale results in the path becoming too short, further mouse movement in the same direction will not change the shape.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestScalingSliderTooSmallRemainsValid()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
AddStep("Add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300, 200) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(0, 50)),
|
||||||
|
new PathControlPoint(new Vector2(0, 100))
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(300));
|
||||||
|
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
double distanceBefore = 0;
|
||||||
|
|
||||||
|
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||||
|
|
||||||
|
AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<SelectionBoxDragHandle>().Skip(1).First()));
|
||||||
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
moveMouse(new Vector2(300, 300));
|
||||||
|
moveMouse(new Vector2(300, 250));
|
||||||
|
moveMouse(new Vector2(300, 200));
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||||
|
AddAssert("ensure slider still has valid length", () => slider.Path.Distance > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouse(Vector2 pos) =>
|
||||||
|
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||||
|
}
|
||||||
|
}
|
@ -41,9 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
addClickStep(MouseButton.Left);
|
addClickStep(MouseButton.Left);
|
||||||
addClickStep(MouseButton.Right);
|
addClickStep(MouseButton.Right);
|
||||||
|
|
||||||
assertPlaced(true);
|
assertPlaced(false);
|
||||||
assertLength(0);
|
|
||||||
assertControlPointType(0, PathType.Linear);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
72
osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs
Normal file
72
osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSliderScaling : TestSceneOsuEditor
|
||||||
|
{
|
||||||
|
private OsuPlayfield playfield;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
AddStep("get playfield", () => playfield = Editor.ChildrenOfType<OsuPlayfield>().First());
|
||||||
|
AddStep("seek to first timing point", () => EditorClock.Seek(Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.First().Time));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScalingLinearSlider()
|
||||||
|
{
|
||||||
|
Slider slider = null;
|
||||||
|
|
||||||
|
AddStep("Add slider", () =>
|
||||||
|
{
|
||||||
|
slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) };
|
||||||
|
|
||||||
|
PathControlPoint[] points =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(100, 0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(points);
|
||||||
|
EditorBeatmap.Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1);
|
||||||
|
|
||||||
|
moveMouse(new Vector2(300));
|
||||||
|
AddStep("select slider", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
double distanceBefore = 0;
|
||||||
|
|
||||||
|
AddStep("store distance", () => distanceBefore = slider.Path.Distance);
|
||||||
|
|
||||||
|
AddStep("move mouse to handle", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<SelectionBoxDragHandle>().Skip(1).First()));
|
||||||
|
AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left));
|
||||||
|
moveMouse(new Vector2(300, 300));
|
||||||
|
AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||||
|
|
||||||
|
AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouse(Vector2 pos) =>
|
||||||
|
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||||
|
}
|
||||||
|
}
|
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png
Executable file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursor.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursortrail.png
Executable file
BIN
osu.Game.Rulesets.Osu.Tests/Resources/old-skin/cursortrail.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
|
|
||||||
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||||
|
|
||||||
public event Action SourceChanged
|
public event Action SourceChanged
|
||||||
{
|
{
|
||||||
|
@ -4,13 +4,22 @@
|
|||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -21,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[Cached]
|
[Cached]
|
||||||
private GameplayBeatmap gameplayBeatmap;
|
private GameplayBeatmap gameplayBeatmap;
|
||||||
|
|
||||||
private ClickingCursorContainer lastContainer;
|
private OsuCursorContainer lastContainer;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
@ -48,12 +57,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
config.SetValue(OsuSetting.AutoCursorSize, true);
|
config.SetValue(OsuSetting.AutoCursorSize, true);
|
||||||
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
|
gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
|
||||||
Scheduler.AddOnce(recreate);
|
Scheduler.AddOnce(() => loadContent(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("test cursor container", recreate);
|
AddStep("test cursor container", () => loadContent(false));
|
||||||
|
|
||||||
void recreate() => SetContents(() => new OsuInputManager(new OsuRuleset().RulesetInfo) { Child = new OsuCursorContainer() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(1, 1)]
|
[TestCase(1, 1)]
|
||||||
@ -68,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
|
AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
|
||||||
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
|
||||||
|
|
||||||
AddStep("load content", loadContent);
|
AddStep("load content", () => loadContent());
|
||||||
|
|
||||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
|
||||||
|
|
||||||
@ -82,18 +89,46 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
|
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadContent()
|
[Test]
|
||||||
|
public void TestTopLeftOrigin()
|
||||||
{
|
{
|
||||||
SetContents(() => new MovingCursorInputManager
|
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
|
||||||
{
|
|
||||||
Child = lastContainer = new ClickingCursorContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null)
|
||||||
|
{
|
||||||
|
SetContents(() =>
|
||||||
|
{
|
||||||
|
var inputManager = automated ? (InputManager)new MovingCursorInputManager() : new OsuInputManager(new OsuRuleset().RulesetInfo);
|
||||||
|
var skinContainer = skinProvider?.Invoke() ?? new SkinProvidingContainer(null);
|
||||||
|
|
||||||
|
lastContainer = automated ? new ClickingCursorContainer() : new OsuCursorContainer();
|
||||||
|
|
||||||
|
return inputManager.WithChild(skinContainer.WithChild(lastContainer));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TopLeftCursorSkin : ISkin
|
||||||
|
{
|
||||||
|
public Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
public ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
|
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
switch (lookup)
|
||||||
|
{
|
||||||
|
case OsuSkinConfiguration osuLookup:
|
||||||
|
if (osuLookup == OsuSkinConfiguration.CursorCentre)
|
||||||
|
return SkinUtils.As<TValue>(new BindableBool(false));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ClickingCursorContainer : OsuCursorContainer
|
private class ClickingCursorContainer : OsuCursorContainer
|
||||||
{
|
{
|
||||||
private bool pressed;
|
private bool pressed;
|
||||||
|
@ -185,6 +185,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
|
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position.Value).ToArray();
|
||||||
|
var oldPosition = slider.Position;
|
||||||
|
var oldStartTime = slider.StartTime;
|
||||||
|
|
||||||
if (ControlPoint == slider.Path.ControlPoints[0])
|
if (ControlPoint == slider.Path.ControlPoints[0])
|
||||||
{
|
{
|
||||||
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||||
@ -202,6 +206,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
else
|
else
|
||||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||||
|
|
||||||
|
if (!slider.Path.HasValidLength)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
|
slider.Path.ControlPoints[i].Position.Value = oldControlPoints[i];
|
||||||
|
|
||||||
|
slider.Position = oldPosition;
|
||||||
|
slider.StartTime = oldStartTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||||
PointsInSegment[0].Type.Value = dragPathType;
|
PointsInSegment[0].Type.Value = dragPathType;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
private PlacementState state;
|
private SliderPlacementState state;
|
||||||
private PathControlPoint segmentStart;
|
private PathControlPoint segmentStart;
|
||||||
private PathControlPoint cursor;
|
private PathControlPoint cursor;
|
||||||
private int currentSegmentLength;
|
private int currentSegmentLength;
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
|
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(SliderPlacementState.Initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -73,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case PlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
HitObject.Position = ToLocalSpace(result.ScreenSpacePosition);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case SliderPlacementState.Body:
|
||||||
updateCursor();
|
updateCursor();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -91,11 +91,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case PlacementState.Initial:
|
case SliderPlacementState.Initial:
|
||||||
beginCurve();
|
beginCurve();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case SliderPlacementState.Body:
|
||||||
if (canPlaceNewControlPoint(out var lastPoint))
|
if (canPlaceNewControlPoint(out var lastPoint))
|
||||||
{
|
{
|
||||||
// Place a new point by detatching the current cursor.
|
// Place a new point by detatching the current cursor.
|
||||||
@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
protected override void OnMouseUp(MouseUpEvent e)
|
||||||
{
|
{
|
||||||
if (state == PlacementState.Body && e.Button == MouseButton.Right)
|
if (state == SliderPlacementState.Body && e.Button == MouseButton.Right)
|
||||||
endCurve();
|
endCurve();
|
||||||
base.OnMouseUp(e);
|
base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
@ -129,13 +129,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void beginCurve()
|
private void beginCurve()
|
||||||
{
|
{
|
||||||
BeginPlacement(commitStart: true);
|
BeginPlacement(commitStart: true);
|
||||||
setState(PlacementState.Body);
|
setState(SliderPlacementState.Body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endCurve()
|
private void endCurve()
|
||||||
{
|
{
|
||||||
updateSlider();
|
updateSlider();
|
||||||
EndPlacement(true);
|
EndPlacement(HitObject.Path.HasValidLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -219,12 +219,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
|
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setState(PlacementState newState)
|
private void setState(SliderPlacementState newState)
|
||||||
{
|
{
|
||||||
state = newState;
|
state = newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum PlacementState
|
private enum SliderPlacementState
|
||||||
{
|
{
|
||||||
Initial,
|
Initial,
|
||||||
Body,
|
Body,
|
||||||
|
@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
|
||||||
if (controlPoints.Count <= 1)
|
if (controlPoints.Count <= 1 || !slider.HitObject.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
placementHandler?.Delete(HitObject);
|
placementHandler?.Delete(HitObject);
|
||||||
return;
|
return;
|
||||||
|
@ -208,7 +208,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
||||||
|
|
||||||
Vector2 pathRelativeDeltaScale = new Vector2(1 + scale.X / sliderQuad.Width, 1 + scale.Y / sliderQuad.Height);
|
Vector2 pathRelativeDeltaScale = new Vector2(
|
||||||
|
sliderQuad.Width == 0 ? 0 : 1 + scale.X / sliderQuad.Width,
|
||||||
|
sliderQuad.Height == 0 ? 0 : 1 + scale.Y / sliderQuad.Height);
|
||||||
|
|
||||||
Queue<Vector2> oldControlPoints = new Queue<Vector2>();
|
Queue<Vector2> oldControlPoints = new Queue<Vector2>();
|
||||||
|
|
||||||
@ -226,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
|
||||||
|
|
||||||
if (xInBounds && yInBounds)
|
if (xInBounds && yInBounds && slider.Path.HasValidLength)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
|
@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
buttonIndex = 0;
|
buttonIndex = 0;
|
||||||
|
|
||||||
AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
|
|
||||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
|
||||||
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
|
||||||
|
|
||||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Replays
|
namespace osu.Game.Rulesets.Osu.Replays
|
||||||
{
|
{
|
||||||
@ -21,24 +19,11 @@ namespace osu.Game.Rulesets.Osu.Replays
|
|||||||
|
|
||||||
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
|
||||||
|
|
||||||
protected Vector2? Position
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var frame = CurrentFrame;
|
|
||||||
|
|
||||||
if (frame == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
Debug.Assert(CurrentTime != null);
|
|
||||||
|
|
||||||
return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void CollectPendingInputs(List<IInput> inputs)
|
public override void CollectPendingInputs(List<IInput> inputs)
|
||||||
{
|
{
|
||||||
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(Position ?? Vector2.Zero) });
|
var position = Interpolation.ValueAt(CurrentTime, StartFrame.Position, EndFrame.Position, StartFrame.Time, EndFrame.Time);
|
||||||
|
|
||||||
|
inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(position) });
|
||||||
inputs.Add(new ReplayState<OsuAction> { PressedActions = CurrentFrame?.Actions ?? new List<OsuAction>() });
|
inputs.Add(new ReplayState<OsuAction> { PressedActions = CurrentFrame?.Actions ?? new List<OsuAction>() });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin)
|
private void load(ISkinSource skin)
|
||||||
{
|
{
|
||||||
|
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||||
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
@ -32,13 +33,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursor"),
|
Texture = skin.GetTexture("cursor"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||||
},
|
},
|
||||||
new NonPlayfieldSprite
|
new NonPlayfieldSprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursormiddle"),
|
Texture = skin.GetTexture("cursormiddle"),
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = centre ? Anchor.Centre : Anchor.TopLeft,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Texture = skin.GetTexture("cursortrail");
|
Texture = skin.GetTexture("cursortrail");
|
||||||
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
||||||
|
|
||||||
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
|
if (disjointTrail)
|
||||||
|
{
|
||||||
|
bool centre = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorCentre)?.Value ?? true;
|
||||||
|
|
||||||
|
TrailOrigin = centre ? Anchor.Centre : Anchor.TopLeft;
|
||||||
|
Blending = BlendingParameters.Inherit;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
}
|
||||||
|
|
||||||
if (Texture != null)
|
if (Texture != null)
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
SliderBorderSize,
|
SliderBorderSize,
|
||||||
SliderPathRadius,
|
SliderPathRadius,
|
||||||
AllowSliderBallTint,
|
AllowSliderBallTint,
|
||||||
|
CursorCentre,
|
||||||
CursorExpand,
|
CursorExpand,
|
||||||
CursorRotate,
|
CursorRotate,
|
||||||
HitCircleOverlayAboveNumber,
|
HitCircleOverlayAboveNumber,
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Batches;
|
using osu.Framework.Graphics.Batches;
|
||||||
using osu.Framework.Graphics.OpenGL.Vertices;
|
using osu.Framework.Graphics.OpenGL.Vertices;
|
||||||
@ -31,6 +32,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private double timeOffset;
|
private double timeOffset;
|
||||||
private float time;
|
private float time;
|
||||||
|
|
||||||
|
private Anchor trailOrigin = Anchor.Centre;
|
||||||
|
|
||||||
|
protected Anchor TrailOrigin
|
||||||
|
{
|
||||||
|
get => trailOrigin;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
trailOrigin = value;
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public CursorTrail()
|
public CursorTrail()
|
||||||
{
|
{
|
||||||
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
|
// as we are currently very dependent on having a running clock, let's make our own clock for the time being.
|
||||||
@ -197,6 +210,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
|
|
||||||
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
|
|
||||||
public TrailDrawNode(CursorTrail source)
|
public TrailDrawNode(CursorTrail source)
|
||||||
@ -213,6 +228,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
size = Source.partSize;
|
size = Source.partSize;
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
|
|
||||||
|
originPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
if (Source.TrailOrigin.HasFlagFast(Anchor.x1))
|
||||||
|
originPosition.X = 0.5f;
|
||||||
|
else if (Source.TrailOrigin.HasFlagFast(Anchor.x2))
|
||||||
|
originPosition.X = 1f;
|
||||||
|
|
||||||
|
if (Source.TrailOrigin.HasFlagFast(Anchor.y1))
|
||||||
|
originPosition.Y = 0.5f;
|
||||||
|
else if (Source.TrailOrigin.HasFlagFast(Anchor.y2))
|
||||||
|
originPosition.Y = 1f;
|
||||||
|
|
||||||
Source.parts.CopyTo(parts, 0);
|
Source.parts.CopyTo(parts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomLeft,
|
TexturePosition = textureRect.BottomLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
|
||||||
@ -246,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)),
|
||||||
TexturePosition = textureRect.BottomRight,
|
TexturePosition = textureRect.BottomRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
Colour = DrawColourInfo.Colour.BottomRight.Linear,
|
||||||
@ -255,7 +282,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopRight,
|
TexturePosition = textureRect.TopRight,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
Colour = DrawColourInfo.Colour.TopRight.Linear,
|
||||||
@ -264,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
vertexBatch.Add(new TexturedTrailVertex
|
vertexBatch.Add(new TexturedTrailVertex
|
||||||
{
|
{
|
||||||
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
|
Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y),
|
||||||
TexturePosition = textureRect.TopLeft,
|
TexturePosition = textureRect.TopLeft,
|
||||||
TextureRect = new Vector4(0, 0, 1, 1),
|
TextureRect = new Vector4(0, 0, 1, 1),
|
||||||
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
Colour = DrawColourInfo.Colour.TopLeft.Linear,
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.UpdateTimeAndPosition(result);
|
base.UpdateTimeAndPosition(result);
|
||||||
|
|
||||||
if (PlacementActive)
|
if (PlacementActive == PlacementState.Active)
|
||||||
{
|
{
|
||||||
if (result.Time is double dragTime)
|
if (result.Time is double dragTime)
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
|||||||
|
|
||||||
bool hitButton = true;
|
bool hitButton = true;
|
||||||
|
|
||||||
Frames.Add(new TaikoReplayFrame(-100000));
|
|
||||||
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
|
Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
|
||||||
|
|
||||||
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Online.Multiplayer;
|
|||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -128,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType<MultiplayerReadyButton>().Single().ChildrenOfType<ReadyButton>().Single().Enabled.Value);
|
||||||
|
|
||||||
AddStep("click ready button", () =>
|
AddStep("click ready button", () =>
|
||||||
{
|
{
|
||||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single());
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -225,7 +226,7 @@ namespace osu.Game.Overlays.Volume
|
|||||||
private set => Bindable.Value = value;
|
private set => Bindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const double adjust_step = 0.05;
|
private const double adjust_step = 0.01;
|
||||||
|
|
||||||
public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
|
public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise);
|
||||||
public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
|
public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise);
|
||||||
@ -233,18 +234,42 @@ namespace osu.Game.Overlays.Volume
|
|||||||
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
|
// because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
|
||||||
private double scrollAccumulation;
|
private double scrollAccumulation;
|
||||||
|
|
||||||
|
private double accelerationModifier = 1;
|
||||||
|
|
||||||
|
private const double max_acceleration = 5;
|
||||||
|
private const double acceleration_multiplier = 1.8;
|
||||||
|
|
||||||
|
private ScheduledDelegate accelerationDebounce;
|
||||||
|
|
||||||
|
private void resetAcceleration() => accelerationModifier = 1;
|
||||||
|
|
||||||
private void adjust(double delta, bool isPrecise)
|
private void adjust(double delta, bool isPrecise)
|
||||||
{
|
{
|
||||||
scrollAccumulation += delta * adjust_step * (isPrecise ? 0.1 : 1);
|
// every adjust increment increases the rate at which adjustments happen up to a cutoff.
|
||||||
|
// this debounce will reset on inactivity.
|
||||||
|
accelerationDebounce?.Cancel();
|
||||||
|
accelerationDebounce = Scheduler.AddDelayed(resetAcceleration, 150);
|
||||||
|
|
||||||
|
delta *= accelerationModifier;
|
||||||
|
accelerationModifier = Math.Min(max_acceleration, accelerationModifier * acceleration_multiplier);
|
||||||
|
|
||||||
var precision = Bindable.Precision;
|
var precision = Bindable.Precision;
|
||||||
|
|
||||||
|
if (isPrecise)
|
||||||
|
{
|
||||||
|
scrollAccumulation += delta * adjust_step * 0.1;
|
||||||
|
|
||||||
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
|
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
|
||||||
{
|
{
|
||||||
Volume += Math.Sign(scrollAccumulation) * precision;
|
Volume += Math.Sign(scrollAccumulation) * precision;
|
||||||
scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision);
|
scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Volume += Math.Sign(delta) * Math.Max(precision, Math.Abs(delta * adjust_step));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
|
/// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PlacementActive { get; private set; }
|
public PlacementState PlacementActive { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="HitObject"/> that is being placed.
|
/// The <see cref="HitObject"/> that is being placed.
|
||||||
@ -72,7 +72,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
protected void BeginPlacement(bool commitStart = false)
|
protected void BeginPlacement(bool commitStart = false)
|
||||||
{
|
{
|
||||||
placementHandler.BeginPlacement(HitObject);
|
placementHandler.BeginPlacement(HitObject);
|
||||||
PlacementActive |= commitStart;
|
if (commitStart)
|
||||||
|
PlacementActive = PlacementState.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,10 +83,19 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <param name="commit">Whether the object should be committed.</param>
|
/// <param name="commit">Whether the object should be committed.</param>
|
||||||
public void EndPlacement(bool commit)
|
public void EndPlacement(bool commit)
|
||||||
{
|
{
|
||||||
if (!PlacementActive)
|
switch (PlacementActive)
|
||||||
|
{
|
||||||
|
case PlacementState.Finished:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case PlacementState.Waiting:
|
||||||
|
// ensure placement was started before ending to make state handling simpler.
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
placementHandler.EndPlacement(HitObject, commit);
|
placementHandler.EndPlacement(HitObject, commit);
|
||||||
PlacementActive = false;
|
PlacementActive = PlacementState.Finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -94,7 +104,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// <param name="result">The snap result information.</param>
|
/// <param name="result">The snap result information.</param>
|
||||||
public virtual void UpdateTimeAndPosition(SnapResult result)
|
public virtual void UpdateTimeAndPosition(SnapResult result)
|
||||||
{
|
{
|
||||||
if (!PlacementActive)
|
if (PlacementActive == PlacementState.Waiting)
|
||||||
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,5 +135,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PlacementState
|
||||||
|
{
|
||||||
|
Waiting,
|
||||||
|
Active,
|
||||||
|
Finished
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -170,7 +171,12 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
target.UnbindFrom(sourceBindable);
|
target.UnbindFrom(sourceBindable);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
target.Parse(source);
|
{
|
||||||
|
if (!(target is IParseable parseable))
|
||||||
|
throw new InvalidOperationException($"Bindable type {target.GetType().ReadableName()} is not {nameof(IParseable)}.");
|
||||||
|
|
||||||
|
parseable.Parse(source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(IMod other) => other is Mod them && Equals(them);
|
public bool Equals(IMod other) => other is Mod them && Equals(them);
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Bindable<double?> ExpectedDistance = new Bindable<double?>();
|
public readonly Bindable<double?> ExpectedDistance = new Bindable<double?>();
|
||||||
|
|
||||||
|
public bool HasValidLength => Distance > 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The control points of the path.
|
/// The control points of the path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -31,32 +33,42 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
/// The current time is always between the start and the end time of the current frame.
|
/// The current time is always between the start and the end time of the current frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Returns null if the current time is strictly before the first frame.</remarks>
|
/// <remarks>Returns null if the current time is strictly before the first frame.</remarks>
|
||||||
|
public TFrame? CurrentFrame => currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The next frame of the replay.
|
||||||
|
/// The start time of <see cref="NextFrame"/> is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Returns null if the current frame is the last frame.</remarks>
|
||||||
|
public TFrame? NextFrame => currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The frame for the start value of the interpolation of the replay movement.
|
||||||
|
/// </summary>
|
||||||
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
||||||
public TFrame CurrentFrame
|
public TFrame StartFrame
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames)
|
if (!HasFrames)
|
||||||
throw new InvalidOperationException($"Attempted to get {nameof(CurrentFrame)} of an empty replay");
|
throw new InvalidOperationException($"Attempted to get {nameof(StartFrame)} of an empty replay");
|
||||||
|
|
||||||
return currentFrameIndex == -1 ? null : (TFrame)Frames[currentFrameIndex];
|
return (TFrame)Frames[Math.Max(0, currentFrameIndex)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next frame of the replay.
|
/// The frame for the end value of the interpolation of the replay movement.
|
||||||
/// The start time is always greater or equal to the start time of <see cref="CurrentFrame"/> regardless of the seeking direction.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Returns null if the current frame is the last frame.</remarks>
|
|
||||||
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
/// <exception cref="InvalidOperationException">The replay is empty.</exception>
|
||||||
public TFrame NextFrame
|
public TFrame EndFrame
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames)
|
if (!HasFrames)
|
||||||
throw new InvalidOperationException($"Attempted to get {nameof(NextFrame)} of an empty replay");
|
throw new InvalidOperationException($"Attempted to get {nameof(EndFrame)} of an empty replay");
|
||||||
|
|
||||||
return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex + 1];
|
return (TFrame)Frames[Math.Min(currentFrameIndex + 1, Frames.Count - 1)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +81,7 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
// This input handler should be enabled only if there is at least one replay frame.
|
// This input handler should be enabled only if there is at least one replay frame.
|
||||||
public override bool IsActive => HasFrames;
|
public override bool IsActive => HasFrames;
|
||||||
|
|
||||||
// Can make it non-null but that is a breaking change.
|
protected double CurrentTime { get; private set; }
|
||||||
protected double? CurrentTime { get; private set; }
|
|
||||||
|
|
||||||
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
|
protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2;
|
||||||
|
|
||||||
@ -97,11 +108,11 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!HasFrames || !FrameAccuratePlayback || CurrentFrame == null)
|
if (!HasFrames || !FrameAccuratePlayback || currentFrameIndex == -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return IsImportant(CurrentFrame) && // a button is in a pressed state
|
return IsImportant(StartFrame) && // a button is in a pressed state
|
||||||
Math.Abs(CurrentTime - NextFrame.Time ?? 0) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span
|
Math.Abs(CurrentTime - EndFrame.Time) <= AllowedImportantTimeSpan; // the next frame is within an allowable time span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +162,7 @@ namespace osu.Game.Rulesets.Replays
|
|||||||
CurrentTime = Math.Clamp(time, frameStart, frameEnd);
|
CurrentTime = Math.Clamp(time, frameStart, frameEnd);
|
||||||
|
|
||||||
// In an important section, a mid-frame time cannot be used and a null is returned instead.
|
// In an important section, a mid-frame time cannot be used and a null is returned instead.
|
||||||
return inImportantSection && frameStart < time && time < frameEnd ? null : CurrentTime;
|
return inImportantSection && frameStart < time && time < frameEnd ? null : (double?)CurrentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getFrameTime(int index)
|
private double getFrameTime(int index)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -11,7 +10,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Input.StateChanges;
|
|
||||||
using osu.Framework.Input.StateChanges.Events;
|
using osu.Framework.Input.StateChanges.Events;
|
||||||
using osu.Framework.Input.States;
|
using osu.Framework.Input.States;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -102,17 +100,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// to avoid allocation
|
|
||||||
private readonly List<IInput> emptyInputList = new List<IInput>();
|
|
||||||
|
|
||||||
protected override List<IInput> GetPendingInputs()
|
|
||||||
{
|
|
||||||
if (replayInputHandler?.IsActive == false)
|
|
||||||
return emptyInputList;
|
|
||||||
|
|
||||||
return base.GetPendingInputs();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Setting application (disables etc.)
|
#region Setting application (disables etc.)
|
||||||
|
|
||||||
private Bindable<bool> mouseDisabled;
|
private Bindable<bool> mouseDisabled;
|
||||||
|
@ -438,8 +438,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
private void onBlueprintDeselected(SelectionBlueprint blueprint)
|
private void onBlueprintDeselected(SelectionBlueprint blueprint)
|
||||||
{
|
{
|
||||||
SelectionHandler.HandleDeselected(blueprint);
|
|
||||||
SelectionBlueprints.ChangeChildDepth(blueprint, 0);
|
SelectionBlueprints.ChangeChildDepth(blueprint, 0);
|
||||||
|
SelectionHandler.HandleDeselected(blueprint);
|
||||||
|
|
||||||
Composer.Playfield.SetKeepAlive(blueprint.HitObject, false);
|
Composer.Playfield.SetKeepAlive(blueprint.HitObject, false);
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private void refreshTool()
|
private void refreshTool()
|
||||||
{
|
{
|
||||||
removePlacement();
|
removePlacement();
|
||||||
createPlacement();
|
ensurePlacementCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePlacementPosition()
|
private void updatePlacementPosition()
|
||||||
@ -215,17 +215,28 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (Composer.CursorInPlacementArea)
|
|
||||||
createPlacement();
|
|
||||||
else if (currentPlacement?.PlacementActive == false)
|
|
||||||
removePlacement();
|
|
||||||
|
|
||||||
if (currentPlacement != null)
|
if (currentPlacement != null)
|
||||||
{
|
{
|
||||||
updatePlacementPosition();
|
switch (currentPlacement.PlacementActive)
|
||||||
|
{
|
||||||
|
case PlacementBlueprint.PlacementState.Waiting:
|
||||||
|
if (!Composer.CursorInPlacementArea)
|
||||||
|
removePlacement();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlacementBlueprint.PlacementState.Finished:
|
||||||
|
removePlacement();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Composer.CursorInPlacementArea)
|
||||||
|
ensurePlacementCreated();
|
||||||
|
|
||||||
|
if (currentPlacement != null)
|
||||||
|
updatePlacementPosition();
|
||||||
|
}
|
||||||
|
|
||||||
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
|
||||||
{
|
{
|
||||||
var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject);
|
var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject);
|
||||||
@ -249,7 +260,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
NewCombo.Value = TernaryState.False;
|
NewCombo.Value = TernaryState.False;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createPlacement()
|
private void ensurePlacementCreated()
|
||||||
{
|
{
|
||||||
if (currentPlacement != null) return;
|
if (currentPlacement != null) return;
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
this.userContent = userContent;
|
this.userContent = userContent;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = timeline_height;
|
||||||
|
|
||||||
ZoomDuration = 200;
|
ZoomDuration = 200;
|
||||||
ZoomEasing = Easing.OutQuint;
|
ZoomEasing = Easing.OutQuint;
|
||||||
@ -127,7 +128,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
});
|
});
|
||||||
|
|
||||||
waveformOpacity = config.GetBindable<float>(OsuSetting.EditorWaveformOpacity);
|
waveformOpacity = config.GetBindable<float>(OsuSetting.EditorWaveformOpacity);
|
||||||
|
|
||||||
Beatmap.BindTo(beatmap);
|
Beatmap.BindTo(beatmap);
|
||||||
|
Beatmap.BindValueChanged(b =>
|
||||||
|
{
|
||||||
|
waveform.Waveform = b.NewValue.Waveform;
|
||||||
|
track = b.NewValue.Track;
|
||||||
|
|
||||||
|
// todo: i don't think this is safe, the track may not be loaded yet.
|
||||||
|
if (track.Length > 0)
|
||||||
|
{
|
||||||
|
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
|
||||||
|
MinZoom = getZoomLevelForVisibleMilliseconds(10000);
|
||||||
|
Zoom = getZoomLevelForVisibleMilliseconds(2000);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -157,20 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
mainContent.Delay(180).MoveToY(0, 200, Easing.OutQuint);
|
mainContent.Delay(180).MoveToY(0, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Beatmap.BindValueChanged(b =>
|
|
||||||
{
|
|
||||||
waveform.Waveform = b.NewValue.Waveform;
|
|
||||||
track = b.NewValue.Track;
|
|
||||||
|
|
||||||
// todo: i don't think this is safe, the track may not be loaded yet.
|
|
||||||
if (track.Length > 0)
|
|
||||||
{
|
|
||||||
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
|
|
||||||
MinZoom = getZoomLevelForVisibleMilliseconds(10000);
|
|
||||||
Zoom = getZoomLevelForVisibleMilliseconds(2000);
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateWaveformOpacity() =>
|
private void updateWaveformOpacity() =>
|
||||||
|
@ -40,7 +40,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
private Bindable<int> indexInCurrentComboBindable;
|
private Bindable<int> indexInCurrentComboBindable;
|
||||||
private Bindable<int> comboIndexBindable;
|
private Bindable<int> comboIndexBindable;
|
||||||
|
|
||||||
private readonly Drawable circle;
|
private readonly ExtendableCircle circle;
|
||||||
|
private readonly Border border;
|
||||||
|
|
||||||
private readonly Container colouredComponents;
|
private readonly Container colouredComponents;
|
||||||
private readonly OsuSpriteText comboIndexText;
|
private readonly OsuSpriteText comboIndexText;
|
||||||
@ -62,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = circle_size;
|
Height = circle_size;
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
circle = new ExtendableCircle
|
circle = new ExtendableCircle
|
||||||
{
|
{
|
||||||
@ -70,6 +71,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
|
border = new Border
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
},
|
||||||
colouredComponents = new Container
|
colouredComponents = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -116,11 +123,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
protected override void OnSelected()
|
protected override void OnSelected()
|
||||||
{
|
{
|
||||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||||
|
updateComboColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDeselected()
|
protected override void OnDeselected()
|
||||||
{
|
{
|
||||||
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
// base logic hides selected blueprints when not selected, but timeline doesn't do that.
|
||||||
|
updateComboColour();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
private void updateComboIndex() => comboIndexText.Text = (indexInCurrentComboBindable.Value + 1).ToString();
|
||||||
@ -133,7 +142,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
var comboColours = skin.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value ?? Array.Empty<Color4>();
|
||||||
var comboColour = combo.GetComboColour(comboColours);
|
var comboColour = combo.GetComboColour(comboColours);
|
||||||
|
|
||||||
if (HitObject is IHasDuration)
|
if (IsSelected)
|
||||||
|
{
|
||||||
|
border.Show();
|
||||||
|
comboColour = comboColour.Lighten(0.3f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
border.Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (HitObject is IHasDuration duration && duration.Duration > 0)
|
||||||
circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f));
|
circle.Colour = ColourInfo.GradientHorizontal(comboColour, comboColour.Lighten(0.4f));
|
||||||
else
|
else
|
||||||
circle.Colour = comboColour;
|
circle.Colour = comboColour;
|
||||||
@ -340,22 +359,38 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Border : ExtendableCircle
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Content.Child.Alpha = 0;
|
||||||
|
Content.Child.AlwaysPresent = true;
|
||||||
|
|
||||||
|
Content.BorderColour = colours.Yellow;
|
||||||
|
Content.EdgeEffect = new EdgeEffectParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A circle with externalised end caps so it can take up the full width of a relative width area.
|
/// A circle with externalised end caps so it can take up the full width of a relative width area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExtendableCircle : CompositeDrawable
|
public class ExtendableCircle : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Circle content;
|
protected readonly Circle Content;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => content.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override Quad ScreenSpaceDrawQuad => content.ScreenSpaceDrawQuad;
|
public override Quad ScreenSpaceDrawQuad => Content.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
public ExtendableCircle()
|
public ExtendableCircle()
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding { Horizontal = -circle_size / 2f };
|
Padding = new MarginPadding { Horizontal = -circle_size / 2f };
|
||||||
InternalChild = content = new Circle
|
InternalChild = Content = new Circle
|
||||||
{
|
{
|
||||||
|
BorderColour = OsuColour.Gray(0.75f),
|
||||||
|
BorderThickness = 4,
|
||||||
|
Masking = true,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class GameplayClock : IFrameBasedClock
|
public class GameplayClock : IFrameBasedClock
|
||||||
{
|
{
|
||||||
private readonly IFrameBasedClock underlyingClock;
|
internal readonly IFrameBasedClock UnderlyingClock;
|
||||||
|
|
||||||
public readonly BindableBool IsPaused = new BindableBool();
|
public readonly BindableBool IsPaused = new BindableBool();
|
||||||
|
|
||||||
@ -30,12 +30,12 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public GameplayClock(IFrameBasedClock underlyingClock)
|
public GameplayClock(IFrameBasedClock underlyingClock)
|
||||||
{
|
{
|
||||||
this.underlyingClock = underlyingClock;
|
UnderlyingClock = underlyingClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double CurrentTime => underlyingClock.CurrentTime;
|
public double CurrentTime => UnderlyingClock.CurrentTime;
|
||||||
|
|
||||||
public double Rate => underlyingClock.Rate;
|
public double Rate => UnderlyingClock.Rate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The rate of gameplay when playback is at 100%.
|
/// The rate of gameplay when playback is at 100%.
|
||||||
@ -59,19 +59,19 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRunning => underlyingClock.IsRunning;
|
public bool IsRunning => UnderlyingClock.IsRunning;
|
||||||
|
|
||||||
public void ProcessFrame()
|
public void ProcessFrame()
|
||||||
{
|
{
|
||||||
// intentionally not updating the underlying clock (handled externally).
|
// intentionally not updating the underlying clock (handled externally).
|
||||||
}
|
}
|
||||||
|
|
||||||
public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime;
|
public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime;
|
||||||
|
|
||||||
public double FramesPerSecond => underlyingClock.FramesPerSecond;
|
public double FramesPerSecond => UnderlyingClock.FramesPerSecond;
|
||||||
|
|
||||||
public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo;
|
public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo;
|
||||||
|
|
||||||
public IClock Source => underlyingClock;
|
public IClock Source => UnderlyingClock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,15 +81,13 @@ namespace osu.Game.Screens.Play
|
|||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
if (!IsPaused.Value)
|
if (!IsPaused.Value)
|
||||||
ClockToProcess.ProcessFrame();
|
GameplayClock.UnderlyingClock.ProcessFrame();
|
||||||
|
|
||||||
base.Update();
|
base.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void OnIsPausedChanged(ValueChangedEvent<bool> isPaused);
|
protected abstract void OnIsPausedChanged(ValueChangedEvent<bool> isPaused);
|
||||||
|
|
||||||
protected virtual IFrameBasedClock ClockToProcess => AdjustableClock;
|
|
||||||
|
|
||||||
protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source);
|
protected abstract GameplayClock CreateGameplayClock(IFrameBasedClock source);
|
||||||
|
|
||||||
#region IAdjustableClock
|
#region IAdjustableClock
|
||||||
|
@ -24,9 +24,9 @@ namespace osu.Game.Screens.Play
|
|||||||
[Cached]
|
[Cached]
|
||||||
public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction>
|
public class HUDOverlay : Container, IKeyBindingHandler<GlobalAction>
|
||||||
{
|
{
|
||||||
public const float FADE_DURATION = 400;
|
public const float FADE_DURATION = 300;
|
||||||
|
|
||||||
public const Easing FADE_EASING = Easing.Out;
|
public const Easing FADE_EASING = Easing.OutQuint;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total height of all the top of screen scoring elements.
|
/// The total height of all the top of screen scoring elements.
|
||||||
@ -74,7 +74,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private bool holdingForHUD;
|
private bool holdingForHUD;
|
||||||
|
|
||||||
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
|
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements };
|
||||||
|
|
||||||
public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
public HUDOverlay(ScoreProcessor scoreProcessor, HealthProcessor healthProcessor, DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.415.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.416.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.2.0" />
|
<PackageReference Include="Sentry" Version="3.2.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.415.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.416.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.412.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||||
@ -93,7 +93,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.415.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.416.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Reference in New Issue
Block a user