1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 20:22:55 +08:00

Merge pull request #9883 from swoolcock/confine-during-gameplay

Add "During Gameplay" option for mouse confining
This commit is contained in:
Dean Herbert 2020-10-08 20:25:27 +09:00 committed by GitHub
commit 8dddd8aff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 17 deletions

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -23,33 +24,41 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestGameplayOverlayActivation() public void TestGameplayOverlayActivation()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
} }
[Test] [Test]
public void TestGameplayOverlayActivationPaused() public void TestGameplayOverlayActivationPaused()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("pause gameplay", () => Player.Pause()); AddStep("pause gameplay", () => Player.Pause());
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }
[Test] [Test]
public void TestGameplayOverlayActivationReplayLoaded() public void TestGameplayOverlayActivationReplayLoaded()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true); AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
} }
[Test] [Test]
public void TestGameplayOverlayActivationBreaks() public void TestGameplayOverlayActivationBreaks()
{ {
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime)); AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered); AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
AddAssert("local user not playing", () => !Player.LocalUserPlaying.Value);
AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime)); AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled); AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
AddAssert("local user playing", () => Player.LocalUserPlaying.Value);
} }
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer(); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
@ -57,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class OverlayTestPlayer : TestPlayer protected class OverlayTestPlayer : TestPlayer
{ {
public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value; public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
public new Bindable<bool> LocalUserPlaying => base.LocalUserPlaying;
} }
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Input;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -69,6 +70,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.MouseDisableButtons, false); Set(OsuSetting.MouseDisableButtons, false);
Set(OsuSetting.MouseDisableWheel, false); Set(OsuSetting.MouseDisableWheel, false);
Set(OsuSetting.ConfineMouseMode, OsuConfineMouseMode.DuringGameplay);
// Graphics // Graphics
Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowFpsDisplay, false);
@ -194,6 +196,7 @@ namespace osu.Game.Configuration
FadePlayfieldWhenHealthLow, FadePlayfieldWhenHealthLow,
MouseDisableButtons, MouseDisableButtons,
MouseDisableWheel, MouseDisableWheel,
ConfineMouseMode,
AudioOffset, AudioOffset,
VolumeInactive, VolumeInactive,
MenuMusic, MenuMusic,

View File

@ -0,0 +1,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration;
namespace osu.Game.Input
{
/// <summary>
/// Connects <see cref="OsuSetting.ConfineMouseMode"/> with <see cref="FrameworkSetting.ConfineMouseMode"/>.
/// If <see cref="OsuGame.LocalUserPlaying"/> is true, we should also confine the mouse cursor if it has been
/// requested with <see cref="OsuConfineMouseMode.DuringGameplay"/>.
/// </summary>
public class ConfineMouseTracker : Component
{
private Bindable<ConfineMouseMode> frameworkConfineMode;
private Bindable<OsuConfineMouseMode> osuConfineMode;
private IBindable<bool> localUserPlaying;
[BackgroundDependencyLoader]
private void load(OsuGame game, FrameworkConfigManager frameworkConfigManager, OsuConfigManager osuConfigManager)
{
frameworkConfineMode = frameworkConfigManager.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode);
osuConfineMode = osuConfigManager.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode);
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
osuConfineMode.ValueChanged += _ => updateConfineMode();
localUserPlaying.BindValueChanged(_ => updateConfineMode(), true);
}
private void updateConfineMode()
{
// confine mode is unavailable on some platforms
if (frameworkConfineMode.Disabled)
return;
switch (osuConfineMode.Value)
{
case OsuConfineMouseMode.Never:
frameworkConfineMode.Value = ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Fullscreen:
frameworkConfineMode.Value = ConfineMouseMode.Fullscreen;
break;
case OsuConfineMouseMode.DuringGameplay:
frameworkConfineMode.Value = localUserPlaying.Value ? ConfineMouseMode.Always : ConfineMouseMode.Never;
break;
case OsuConfineMouseMode.Always:
frameworkConfineMode.Value = ConfineMouseMode.Always;
break;
}
}
}
}

View File

@ -0,0 +1,37 @@
// 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.ComponentModel;
using osu.Framework.Input;
namespace osu.Game.Input
{
/// <summary>
/// Determines the situations in which the mouse cursor should be confined to the window.
/// Expands upon <see cref="ConfineMouseMode"/> by providing the option to confine during gameplay.
/// </summary>
public enum OsuConfineMouseMode
{
/// <summary>
/// The mouse cursor will be free to move outside the game window.
/// </summary>
Never,
/// <summary>
/// The mouse cursor will be locked to the window bounds while in fullscreen mode.
/// </summary>
Fullscreen,
/// <summary>
/// The mouse cursor will be locked to the window bounds during gameplay,
/// but may otherwise move freely.
/// </summary>
[Description("During Gameplay")]
DuringGameplay,
/// <summary>
/// The mouse cursor will always be locked to the window bounds while the game has focus.
/// </summary>
Always
}
}

View File

@ -95,6 +95,15 @@ namespace osu.Game
/// </summary> /// </summary>
public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(); public readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>();
/// <summary>
/// Whether the local user is currently interacting with the game in a way that should not be interrupted.
/// </summary>
/// <remarks>
/// This is exclusively managed by <see cref="Player"/>. If other components are mutating this state, a more
/// resilient method should be used to ensure correct state.
/// </remarks>
public Bindable<bool> LocalUserPlaying = new BindableBool();
protected OsuScreenStack ScreenStack; protected OsuScreenStack ScreenStack;
protected BackButton BackButton; protected BackButton BackButton;
@ -577,7 +586,8 @@ namespace osu.Game
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both },
idleTracker idleTracker,
new ConfineMouseTracker()
}); });
ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenPushed += screenPushed;
@ -947,6 +957,9 @@ namespace osu.Game
break; break;
} }
// reset on screen change for sanity.
LocalUserPlaying.Value = false;
if (current is IOsuScreen currentOsuScreen) if (current is IOsuScreen currentOsuScreen)
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);

View File

@ -6,9 +6,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
namespace osu.Game.Overlays.Settings.Sections.Input namespace osu.Game.Overlays.Settings.Sections.Input
{ {
@ -47,10 +47,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
LabelText = "Map absolute input to window", LabelText = "Map absolute input to window",
Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow) Current = config.GetBindable<bool>(FrameworkSetting.MapAbsoluteInputToWindow)
}, },
new SettingsEnumDropdown<ConfineMouseMode> new SettingsEnumDropdown<OsuConfineMouseMode>
{ {
LabelText = "Confine mouse cursor to window", LabelText = "Confine mouse cursor to window",
Current = config.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode), Current = osuConfig.GetBindable<OsuConfineMouseMode>(OsuSetting.ConfineMouseMode)
}, },
new SettingsCheckbox new SettingsCheckbox
{ {

View File

@ -68,6 +68,8 @@ namespace osu.Game.Screens.Play
private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>(); private readonly Bindable<bool> storyboardReplacesBackground = new Bindable<bool>();
protected readonly Bindable<bool> LocalUserPlaying = new Bindable<bool>();
public int RestartCount; public int RestartCount;
[Resolved] [Resolved]
@ -155,8 +157,8 @@ namespace osu.Game.Screens.Play
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay()); DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuConfigManager config) private void load(AudioManager audio, OsuConfigManager config, OsuGame game)
{ {
Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray();
@ -172,6 +174,9 @@ namespace osu.Game.Screens.Play
mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel); mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
if (game != null)
LocalUserPlaying.BindTo(game.LocalUserPlaying);
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor = ruleset.CreateScoreProcessor();
@ -219,9 +224,9 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide(); skipOverlay.Hide();
} }
DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.IsPaused.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode()); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateGameplayState());
breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode()); breakTracker.IsBreakTime.BindValueChanged(_ => updateGameplayState());
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true); DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@ -353,14 +358,11 @@ namespace osu.Game.Screens.Play
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue; HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
} }
private void updateOverlayActivationMode() private void updateGameplayState()
{ {
bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value; bool inGameplay = !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.IsPaused.Value && !breakTracker.IsBreakTime.Value;
OverlayActivationMode.Value = inGameplay ? OverlayActivation.Disabled : OverlayActivation.UserTriggered;
if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays) LocalUserPlaying.Value = inGameplay;
OverlayActivationMode.Value = OverlayActivation.UserTriggered;
else
OverlayActivationMode.Value = OverlayActivation.Disabled;
} }
private void updatePauseOnFocusLostState() => private void updatePauseOnFocusLostState() =>
@ -661,7 +663,7 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>()) foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(musicController.CurrentTrack); mod.ApplyToTrack(musicController.CurrentTrack);
updateOverlayActivationMode(); updateGameplayState();
} }
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)