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. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Humanizer; using Humanizer;
@ -15,6 +16,7 @@
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
using osuTK; using osuTK;
@ -27,7 +29,9 @@ public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<
internal readonly Container<PathControlPointPiece> Pieces; internal readonly Container<PathControlPointPiece> Pieces;
private readonly Container<PathControlPointConnection> connections; private readonly Container<PathControlPointConnection> connections;
private readonly Slider slider; private readonly Slider slider;
private readonly bool allowSelection; private readonly bool allowSelection;
private InputManager inputManager; private InputManager inputManager;
@ -90,7 +94,10 @@ private void removeControlPoints(IEnumerable<PathControlPoint> controlPoints)
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
foreach (var piece in Pieces) foreach (var piece in Pieces)
{
piece.IsSelected.Value = false; piece.IsSelected.Value = false;
}
return false; return false;
} }
@ -164,16 +171,63 @@ public MenuItem[] ContextMenuItems
if (!Pieces.Any(p => p.IsHovered)) if (!Pieces.Any(p => p.IsHovered))
return null; return null;
int selectedPoints = Pieces.Count(p => p.IsSelected.Value); var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
int count = selectedPieces.Count;
if (selectedPoints == 0) if (count == 0)
return null; return null;
List<MenuItem> items = new List<MenuItem>();
if (!selectedPieces.Contains(Pieces[0]))
items.Add(createMenuItemForPathType(null));
// todo: hide/disable items which aren't valid for selected points
items.Add(createMenuItemForPathType(PathType.Linear));
items.Add(createMenuItemForPathType(PathType.PerfectCurve));
items.Add(createMenuItemForPathType(PathType.Bezier));
items.Add(createMenuItemForPathType(PathType.Catmull));
return new MenuItem[] return new MenuItem[]
{ {
new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, 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. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -8,8 +9,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{ {
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject, nextHitObject, hitObject.StackedEndPosition) : base(hitObject.StackedPosition, hitObject.StartTime, nextHitObject?.StartTime)
{ {
Masking = true; Masking = true;
} }

View File

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

View File

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

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

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -9,7 +8,6 @@
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
@ -24,21 +22,21 @@ public abstract class DistanceSnapGrid : CompositeDrawable
/// </summary> /// </summary>
protected float DistanceSpacing { get; private set; } protected float DistanceSpacing { get; private set; }
/// <summary>
/// The snapping time at <see cref="CentrePosition"/>.
/// </summary>
protected double StartTime { get; private set; }
/// <summary> /// <summary>
/// The maximum number of distance snapping intervals allowed. /// The maximum number of distance snapping intervals allowed.
/// </summary> /// </summary>
protected int MaxIntervals { get; private set; } protected int MaxIntervals { get; private set; }
/// <summary> /// <summary>
/// The position which the grid is centred on. /// The position which the grid should start.
/// The first beat snapping tick is located at <see cref="CentrePosition"/> + <see cref="DistanceSpacing"/> in the desired direction. /// The first beat snapping tick is located at <see cref="StartPosition"/> + <see cref="DistanceSpacing"/> away from this point.
/// </summary> /// </summary>
protected readonly Vector2 CentrePosition; protected readonly Vector2 StartPosition;
/// <summary>
/// The snapping time at <see cref="StartPosition"/>.
/// </summary>
protected readonly double StartTime;
[Resolved] [Resolved]
protected OsuColour Colours { get; private set; } protected OsuColour Colours { get; private set; }
@ -53,25 +51,23 @@ public abstract class DistanceSnapGrid : CompositeDrawable
private BindableBeatDivisor beatDivisor { get; set; } private BindableBeatDivisor beatDivisor { get; set; }
private readonly Cached gridCache = new Cached(); private readonly Cached gridCache = new Cached();
private readonly HitObject hitObject; private readonly double? endTime;
private readonly HitObject nextHitObject;
protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) /// <summary>
/// Creates a new <see cref="DistanceSnapGrid"/>.
/// </summary>
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null)
{ {
this.hitObject = hitObject; this.endTime = endTime;
this.nextHitObject = nextHitObject; StartPosition = startPosition;
StartTime = startTime;
CentrePosition = centrePosition;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
[BackgroundDependencyLoader]
private void load()
{
StartTime = hitObject.GetEndTime();
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -83,12 +79,12 @@ private void updateSpacing()
{ {
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime);
if (nextHitObject == null) if (endTime == null)
MaxIntervals = int.MaxValue; MaxIntervals = int.MaxValue;
else else
{ {
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
double maxDuration = nextHitObject.StartTime - StartTime + 1; double maxDuration = endTime.Value - StartTime + 1;
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing));
} }
@ -110,7 +106,7 @@ protected override void Update()
if (!gridCache.IsValid) if (!gridCache.IsValid)
{ {
ClearInternal(); ClearInternal();
CreateContent(CentrePosition); CreateContent(StartPosition);
gridCache.Validate(); gridCache.Validate();
} }
} }
@ -118,7 +114,7 @@ protected override void Update()
/// <summary> /// <summary>
/// Creates the content which visualises the grid ticks. /// Creates the content which visualises the grid ticks.
/// </summary> /// </summary>
protected abstract void CreateContent(Vector2 centrePosition); protected abstract void CreateContent(Vector2 startPosition);
/// <summary> /// <summary>
/// Snaps a position to this grid. /// Snaps a position to this grid.

View File

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

View File

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