Merge branch 'master' into separate-path-connection

This commit is contained in:
Dean Herbert 2019-12-11 19:03:14 +09:00 committed by GitHub
commit d027c982e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 183 additions and 65 deletions

View File

@ -0,0 +1,11 @@
{
"profiles": {
"osu! Desktop": {
"commandName": "Project"
},
"osu! Tournament": {
"commandName": "Project",
"commandLineArgs": "--tournament"
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
@ -15,6 +16,7 @@
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose;
using osuTK;
@ -27,7 +29,9 @@ public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<
internal readonly Container<PathControlPointPiece> Pieces;
private readonly Container<PathControlPointConnection> connections;
private readonly Slider slider;
private readonly bool allowSelection;
private InputManager inputManager;
@ -90,7 +94,10 @@ private void removeControlPoints(IEnumerable<PathControlPoint> controlPoints)
protected override bool OnClick(ClickEvent e)
{
foreach (var piece in Pieces)
{
piece.IsSelected.Value = false;
}
return false;
}
@ -164,16 +171,63 @@ public MenuItem[] ContextMenuItems
if (!Pieces.Any(p => p.IsHovered))
return null;
int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
int count = selectedPieces.Count;
if (selectedPoints == 0)
if (count == 0)
return null;
List<MenuItem> items = new List<MenuItem>();
if (!selectedPieces.Contains(Pieces[0]))
items.Add(createMenuItemForPathType(null));
// todo: hide/disable items which aren't valid for selected points
items.Add(createMenuItemForPathType(PathType.Linear));
items.Add(createMenuItemForPathType(PathType.PerfectCurve));
items.Add(createMenuItemForPathType(PathType.Bezier));
items.Add(createMenuItemForPathType(PathType.Catmull));
return new MenuItem[]
{
new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected())
new OsuMenuItem($"Delete {"control point".ToQuantity(count, count > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()),
new OsuMenuItem("Curve type")
{
Items = items
}
};
}
}
private MenuItem createMenuItemForPathType(PathType? type)
{
int totalCount = Pieces.Count(p => p.IsSelected.Value);
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
var item = new PathTypeMenuItem(type, () =>
{
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
p.ControlPoint.Type.Value = type;
});
if (countOfState == totalCount)
item.State.Value = TernaryState.True;
else if (countOfState > 0)
item.State.Value = TernaryState.Indeterminate;
else
item.State.Value = TernaryState.False;
return item;
}
private class PathTypeMenuItem : TernaryStateMenuItem
{
public PathTypeMenuItem(PathType? type, Action action)
: base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke())
{
}
private static TernaryState changeState(TernaryState state) => TernaryState.True;
}
}
}

View File

@ -1,6 +1,7 @@
// 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 JetBrains.Annotations;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
: base(hitObject, nextHitObject, hitObject.StackedEndPosition)
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
{
Masking = true;
}

View File

@ -7,7 +7,6 @@
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
@ -44,7 +43,7 @@ public void Setup() => Schedule(() =>
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid(new HitObject(), grid_position)
new TestDistanceSnapGrid()
};
});
@ -73,7 +72,7 @@ public void TestLimitedDistance()
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
new TestDistanceSnapGrid(100)
};
});
}
@ -82,68 +81,68 @@ private class TestDistanceSnapGrid : DistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
: base(hitObject, nextHitObject, centrePosition)
public TestDistanceSnapGrid(double? endTime = null)
: base(grid_position, 0, endTime)
{
}
protected override void CreateContent(Vector2 centrePosition)
protected override void CreateContent(Vector2 startPosition)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
Position = centrePosition
Position = startPosition
});
int beatIndex = 0;
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Position = new Vector2(s, startPosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Position = new Vector2(s, startPosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Position = new Vector2(startPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Position = new Vector2(startPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}

View File

@ -285,8 +285,6 @@ private void confirmClockRunning(bool isRunning) =>
protected class PausePlayer : TestPlayer
{
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;

View File

@ -0,0 +1,51 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay
{
[HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
public class TestScenePauseWhenInactive : PlayerTestScene
{
protected new TestPlayer Player => (TestPlayer)base.Player;
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
return beatmap;
}
[Resolved]
private GameHost host { get; set; }
public TestScenePauseWhenInactive()
: base(new OsuRuleset())
{
}
[Test]
public void TestDoesntPauseDuringIntro()
{
AddStep("set inactive", () => ((Bindable<bool>)host.IsActive).Value = false);
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
}
protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
}
}

View File

@ -5,19 +5,18 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
{
protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition)
: base(hitObject, nextHitObject, centrePosition)
protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
: base(startPosition, startTime, endTime)
{
}
protected override void CreateContent(Vector2 centrePosition)
protected override void CreateContent(Vector2 startPosition)
{
const float crosshair_thickness = 1;
const float crosshair_max_size = 10;
@ -27,7 +26,7 @@ protected override void CreateContent(Vector2 centrePosition)
new Box
{
Origin = Anchor.Centre,
Position = centrePosition,
Position = startPosition,
Width = crosshair_thickness,
EdgeSmoothness = new Vector2(1),
Height = Math.Min(crosshair_max_size, DistanceSpacing * 2),
@ -35,15 +34,15 @@ protected override void CreateContent(Vector2 centrePosition)
new Box
{
Origin = Anchor.Centre,
Position = centrePosition,
Position = startPosition,
EdgeSmoothness = new Vector2(1),
Width = Math.Min(crosshair_max_size, DistanceSpacing * 2),
Height = crosshair_thickness,
}
});
float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X);
float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y);
float dx = Math.Max(startPosition.X, DrawWidth - startPosition.X);
float dy = Math.Max(startPosition.Y, DrawHeight - startPosition.Y);
float maxDistance = new Vector2(dx, dy).Length;
int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing));
@ -54,7 +53,7 @@ protected override void CreateContent(Vector2 centrePosition)
AddInternal(new CircularProgress
{
Origin = Anchor.Centre,
Position = centrePosition,
Position = startPosition,
Current = { Value = 1 },
Size = new Vector2(radius),
InnerRadius = 4 * 1f / radius,
@ -66,9 +65,9 @@ protected override void CreateContent(Vector2 centrePosition)
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position)
{
if (MaxIntervals == 0)
return (CentrePosition, StartTime);
return (StartPosition, StartTime);
Vector2 direction = position - CentrePosition;
Vector2 direction = position - StartPosition;
if (direction == Vector2.Zero)
direction = new Vector2(0.001f, 0.001f);
@ -78,9 +77,9 @@ public override (Vector2 position, double time) GetSnappedPosition(Vector2 posit
int radialCount = Math.Clamp((int)MathF.Round(distance / radius), 1, MaxIntervals);
Vector2 normalisedDirection = direction * new Vector2(1f / distance);
Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius;
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length));
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length));
}
}
}

View File

@ -1,7 +1,6 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Graphics;
@ -9,7 +8,6 @@
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
@ -24,21 +22,21 @@ public abstract class DistanceSnapGrid : CompositeDrawable
/// </summary>
protected float DistanceSpacing { get; private set; }
/// <summary>
/// The snapping time at <see cref="CentrePosition"/>.
/// </summary>
protected double StartTime { get; private set; }
/// <summary>
/// The maximum number of distance snapping intervals allowed.
/// </summary>
protected int MaxIntervals { get; private set; }
/// <summary>
/// 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 position which the grid should start.
/// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceSpacing"/> away from this point.
/// </summary>
protected readonly Vector2 CentrePosition;
protected readonly Vector2 StartPosition;
/// <summary>
/// The snapping time at <see cref="StartPosition"/>.
/// </summary>
protected readonly double StartTime;
[Resolved]
protected OsuColour Colours { get; private set; }
@ -53,25 +51,23 @@ public abstract class DistanceSnapGrid : CompositeDrawable
private BindableBeatDivisor beatDivisor { get; set; }
private readonly Cached gridCache = new Cached();
private readonly HitObject hitObject;
private readonly HitObject nextHitObject;
private readonly double? endTime;
protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition)
/// <summary>
/// Creates a new <see cref="DistanceSnapGrid"/>.
/// </summary>
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
{
this.hitObject = hitObject;
this.nextHitObject = nextHitObject;
CentrePosition = centrePosition;
this.endTime = endTime;
StartPosition = startPosition;
StartTime = startTime;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
StartTime = hitObject.GetEndTime();
}
protected override void LoadComplete()
{
base.LoadComplete();
@ -83,12 +79,12 @@ private void updateSpacing()
{
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
if (nextHitObject == null)
if (endTime == 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;
double maxDuration = endTime.Value - StartTime + 1;
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
}
@ -110,7 +106,7 @@ protected override void Update()
if (!gridCache.IsValid)
{
ClearInternal();
CreateContent(CentrePosition);
CreateContent(StartPosition);
gridCache.Validate();
}
}
@ -118,7 +114,7 @@ protected override void Update()
/// <summary>
/// Creates the content which visualises the grid ticks.
/// </summary>
protected abstract void CreateContent(Vector2 centrePosition);
protected abstract void CreateContent(Vector2 startPosition);
/// <summary>
/// Snaps a position to this grid.

View File

@ -135,7 +135,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config)
addGameplayComponents(GameplayClockContainer, working);
addOverlayComponents(GameplayClockContainer, working);
DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true);
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
// bind clock into components that require it
DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused);
@ -146,6 +146,7 @@ private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config)
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
mod.ApplyToScoreProcessor(ScoreProcessor);
breakOverlay.IsBreakTime.ValueChanged += _ => updatePauseOnFocusLostState();
}
private void addUnderlayComponents(Container target)
@ -241,6 +242,11 @@ private void addOverlayComponents(Container target, WorkingBeatmap working)
});
}
private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value
&& !breakOverlay.IsBreakTime.Value;
private WorkingBeatmap loadBeatmap()
{
WorkingBeatmap working = Beatmap.Value;

View File

@ -8,13 +8,16 @@ namespace osu.Game.Tests.Visual
{
public class TestPlayer : Player
{
protected override bool PauseOnFocusLost => false;
protected override bool PauseOnFocusLost { get; }
public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
public TestPlayer(bool allowPause = true, bool showResults = true)
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
public TestPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
: base(allowPause, showResults)
{
PauseOnFocusLost = pauseOnFocusLost;
}
}
}