diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index a89f5fb647..26a37fa211 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Extensions; using osu.Game.Configuration; @@ -58,7 +56,11 @@ namespace osu.Game.Tests.Visual.Navigation // First scroll makes volume controls appear, second adjusts volume. AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10); - AddAssert("Volume is still zero", () => Game.Audio.Volume.Value == 0); + AddAssert("Volume is still zero", () => Game.Audio.Volume.Value, () => Is.Zero); + + AddStep("Pause", () => InputManager.PressKey(Key.Escape)); + AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10); + AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0); } [Test] @@ -80,8 +82,8 @@ namespace osu.Game.Tests.Visual.Navigation private void loadToPlayerNonBreakTime() { - Player player = null; - Screens.Select.SongSelect songSelect = null; + Player? player = null; + Screens.Select.SongSelect songSelect = null!; PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); @@ -95,7 +97,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for play time active", () => !player.IsBreakTime.Value); + AddUntilStep("wait for play time active", () => player!.IsBreakTime.Value, () => Is.False); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs index bb94912c83..e544fb127d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Volume; @@ -59,13 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestAltScrollNotBlocked() { - bool scrollReceived = false; + TestGlobalScrollAdjustsVolume volumeAdjust = null!; - AddStep("add volume control receptor", () => Add(new VolumeControlReceptor + AddStep("add volume control receptor", () => Add(volumeAdjust = new TestGlobalScrollAdjustsVolume { RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, - ScrollActionRequested = (_, _, _) => scrollReceived = true, })); AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft)); @@ -75,10 +75,21 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.ScrollVerticalBy(10); }); - AddAssert("receptor received scroll input", () => scrollReceived); + AddAssert("receptor received scroll input", () => volumeAdjust.ScrollReceived); AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); } + public partial class TestGlobalScrollAdjustsVolume : GlobalScrollAdjustsVolume + { + public bool ScrollReceived { get; private set; } + + protected override bool OnScroll(ScrollEvent e) + { + ScrollReceived = true; + return base.OnScroll(e); + } + } + private partial class TestOverlay : OsuFocusedOverlayContainer { [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs index 52543c68ce..c2b8ec76f4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs @@ -1,8 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Volume; @@ -11,7 +10,14 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneVolumeOverlay : OsuTestScene { - private VolumeOverlay volume; + private VolumeOverlay volume = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(volume = new VolumeOverlay()); + return dependencies; + } protected override void LoadComplete() { @@ -19,12 +25,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddRange(new Drawable[] { - volume = new VolumeOverlay(), - new VolumeControlReceptor + volume, + new GlobalScrollAdjustsVolume { RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action), - ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e808e570c7..244b72edaa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -57,7 +57,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.Toolbar; -using osu.Game.Overlays.Volume; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; @@ -980,12 +979,6 @@ namespace osu.Game AddRange(new Drawable[] { - new VolumeControlReceptor - { - RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action), - ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), - }, ScreenOffsetContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -1432,6 +1425,19 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return volume.Adjust(e.Action); + + case GlobalAction.ToggleMute: + case GlobalAction.NextVolumeMeter: + case GlobalAction.PreviousVolumeMeter: + + if (e.Repeat) + return true; + + return volume.Adjust(e.Action); + case GlobalAction.ToggleFPSDisplay: fpsCounter.ToggleVisibility(); return true; diff --git a/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs new file mode 100644 index 0000000000..f1ad88833b --- /dev/null +++ b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; + +namespace osu.Game.Overlays.Volume +{ + /// + /// Add to a container or screen to make scrolling anywhere in the container cause the global game volume to be adjusted. + /// + /// + /// This is generally expected behaviour in many locations in osu!stable. + /// + public partial class GlobalScrollAdjustsVolume : Container + { + [Resolved] + private VolumeOverlay? volumeOverlay { get; set; } + + public GlobalScrollAdjustsVolume() + { + RelativeSizeAxes = Axes.Both; + } + + protected override bool OnScroll(ScrollEvent e) + { + if (e.ScrollDelta.Y == 0) + return false; + + // forward any unhandled mouse scroll events to the volume control. + return volumeOverlay?.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise) ?? false; + } + } +} diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs deleted file mode 100644 index 2e8d86d4c7..0000000000 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; - -namespace osu.Game.Overlays.Volume -{ - public partial class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalKeyboardInput - { - public Func ActionRequested; - public Func ScrollActionRequested; - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case GlobalAction.DecreaseVolume: - case GlobalAction.IncreaseVolume: - return ActionRequested?.Invoke(e.Action) == true; - - case GlobalAction.ToggleMute: - case GlobalAction.NextVolumeMeter: - case GlobalAction.PreviousVolumeMeter: - if (!e.Repeat) - return ActionRequested?.Invoke(e.Action) == true; - - return false; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - protected override bool OnScroll(ScrollEvent e) - { - if (e.ScrollDelta.Y == 0) - return false; - - // forward any unhandled mouse scroll events to the volume control. - ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); - return true; - } - - public bool OnScroll(KeyBindingScrollEvent e) => - ScrollActionRequested?.Invoke(e.Action, e.ScrollAmount, e.IsPrecise) ?? false; - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0630b9612e..ae1ad4dceb 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -28,6 +28,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.SkinEditor; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; @@ -124,6 +125,7 @@ namespace osu.Game.Screens.Menu AddRangeInternal(new[] { + new GlobalScrollAdjustsVolume(), buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, diff --git a/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs b/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs new file mode 100644 index 0000000000..059d5a0dd4 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . 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.Input.Events; +using osu.Game.Configuration; +using osu.Game.Overlays.Volume; + +namespace osu.Game.Screens.Play +{ + /// + /// Primarily handles volume adjustment in gameplay. + /// + /// - If the user has mouse wheel disabled, only allow during break time or when holding alt. Also block scroll from parent handling. + /// - Otherwise always allow, as per implementation. + /// + internal partial class GameplayScrollWheelHandling : GlobalScrollAdjustsVolume + { + private Bindable mouseWheelDisabled = null!; + + [Resolved] + private IGameplayClock gameplayClock { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + } + + protected override bool OnScroll(ScrollEvent e) + { + // During pause, allow global volume adjust regardless of settings. + if (gameplayClock.IsPaused.Value) + return base.OnScroll(e); + + // Block any parent handling of scroll if the user has asked for it (special case when holding "Alt"). + if (mouseWheelDisabled.Value && !e.AltPressed) + return true; + + return base.OnScroll(e); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a762d2ae82..f6b0230714 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -15,7 +15,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; @@ -88,8 +87,6 @@ namespace osu.Game.Screens.Play private bool isRestarting; private bool skipExitTransition; - private Bindable mouseWheelDisabled; - private readonly Bindable storyboardReplacesBackground = new Bindable(); public IBindable LocalUserPlaying => localUserPlaying; @@ -228,8 +225,6 @@ namespace osu.Game.Screens.Play return; } - mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - if (game != null) gameActive.BindTo(game.IsActive); @@ -251,7 +246,10 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(HealthProcessor); - InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); + InternalChildren = new Drawable[] + { + GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime), + }; AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); @@ -266,6 +264,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, HealthProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); + GameplayClockContainer.Add(new GameplayScrollWheelHandling()); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -894,16 +893,6 @@ namespace osu.Game.Screens.Play }); } - protected override bool OnScroll(ScrollEvent e) - { - // During pause, allow global volume adjust regardless of settings. - if (GameplayClockContainer.IsPaused.Value) - return false; - - // Block global volume adjust if the user has asked for it (special case when holding "Alt"). - return mouseWheelDisabled.Value && !e.AltPressed; - } - #region Gameplay leaderboard protected readonly Bindable LeaderboardExpandedState = new BindableBool(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 20985c20e0..837974a8f2 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -27,6 +27,7 @@ using osu.Game.Input; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Overlays.Volume; using osu.Game.Performance; using osu.Game.Scoring; using osu.Game.Screens.Menu; @@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play InternalChildren = new Drawable[] { + new GlobalScrollAdjustsVolume(), (content = new LogoTrackingContainer { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9f7a2c02ff..210f8203f4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -169,10 +170,12 @@ namespace osu.Game.Screens.Select AddRangeInternal(new Drawable[] { + new GlobalScrollAdjustsVolume(), new VerticalMaskingContainer { Children = new Drawable[] { + new GlobalScrollAdjustsVolume(), new GridContainer // used for max width implementation { RelativeSizeAxes = Axes.Both,