diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 69ae8328e9..3f7560844c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods { public override LocalisableString Description => @"Use the mouse to control the catcher."; - private DrawableRuleset drawableRuleset = null!; + private DrawableCatchRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - this.drawableRuleset = drawableRuleset; + this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset; } public void ApplyToPlayer(Player player) { if (!drawableRuleset.HasReplayLoaded.Value) - drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); + { + var catchPlayfield = (CatchPlayfield)drawableRuleset.Playfield; + catchPlayfield.CatcherArea.Add(new MouseInputHelper(catchPlayfield.CatcherArea)); + } } private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition @@ -38,9 +41,10 @@ namespace osu.Game.Rulesets.Catch.Mods public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public MouseInputHelper(CatchPlayfield playfield) + public MouseInputHelper(CatcherArea catcherArea) { - catcherArea = playfield.CatcherArea; + this.catcherArea = catcherArea; + RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 44404ca245..da6fac3269 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -145,6 +147,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool isBreak() => Player.IsBreakTime.Value; - private bool cursorAlphaAlmostEquals(float alpha) => Precision.AlmostEquals(Player.DrawableRuleset.Cursor.Alpha, alpha, 0.1f); + private OsuPlayfield playfield => (OsuPlayfield)Player.DrawableRuleset.Playfield; + + private bool cursorAlphaAlmostEquals(float alpha) => + Precision.AlmostEquals(playfield.Cursor.AsNonNull().Alpha, alpha, 0.1f) && + Precision.AlmostEquals(playfield.Smoke.Alpha, alpha, 0.1f); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index fbde9e0491..38d90eb121 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Timing; @@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 2f84c30581..d1bbae8e1a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Localisation; @@ -9,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Utils; @@ -33,9 +35,15 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); + var osuPlayfield = (OsuPlayfield)playfield; + Debug.Assert(osuPlayfield.Cursor != null); + + bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(osuPlayfield.Clock.CurrentTime); float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; - playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + float currentAlpha = (float)Interpolation.Lerp(osuPlayfield.Cursor.Alpha, targetAlpha, Math.Clamp(osuPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); + + osuPlayfield.Cursor.Alpha = currentAlpha; + osuPlayfield.Smoke.Alpha = currentAlpha; } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 911363a27e..31a6b69d6b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Localisation; using osu.Framework.Timing; using osu.Framework.Utils; @@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index b08b7b4e85..bb68c7298f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { default: JudgementText + .FadeInFromZero(300, Easing.OutQuint) .ScaleTo(Vector2.One) .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); break; @@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon ringExplosion?.PlayAnimation(); } - public Drawable? GetAboveHitObjectsProxiedContent() => null; + public Drawable? GetAboveHitObjectsProxiedContent() => JudgementText.CreateProxy(); private class RingExplosion : CompositeDrawable { diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs index 6c998e244c..aba4d0ff63 100644 --- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs +++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Rendering.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Framework.Utils; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -185,6 +186,8 @@ namespace osu.Game.Rulesets.Osu.Skinning private float radius; private Vector2 drawSize; private Texture? texture; + private int rotationSeed; + private int rotationIndex; // anim calculation vars (color, scale, direction) private double initialFadeOutDurationTrunc; @@ -194,8 +197,6 @@ namespace osu.Game.Rulesets.Osu.Skinning private double reFadeInTime; private double finalFadeOutTime; - private Random rotationRNG = new Random(); - public SmokeDrawNode(ITexturedShaderDrawable source) : base(source) { @@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Skinning SmokeEndTime = Source.smokeEndTime; CurrentTime = Source.Clock.CurrentTime; - rotationRNG = new Random(Source.rotationSeed); + rotationSeed = Source.rotationSeed; initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc; @@ -233,6 +234,8 @@ namespace osu.Game.Rulesets.Osu.Skinning if (points.Count == 0) return; + rotationIndex = 0; + quadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10); texture ??= renderer.WhitePixel; RectangleF textureRect = texture.GetTextureRect(); @@ -311,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); } - private float nextRotation() => max_rotation * ((float)rotationRNG.NextDouble() * 2 - 1); + private float nextRotation() => max_rotation * (StatelessRNG.NextSingle(rotationSeed, rotationIndex++) * 2 - 1); private void drawPointQuad(SmokePoint point, RectangleF textureRect) { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2e67e91460..e9a6c84c0b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; + public SmokeContainer Smoke { get; } public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI InternalChildren = new Drawable[] { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, - new SmokeContainer { RelativeSizeAxes = Axes.Both }, + Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 1858aee76b..89c5b9b23b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Editing } } }, - new MenuCursor() + new MenuCursorContainer() }; scrollContainer.Add(innerBox = new Box diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index cf62c73ad4..6070b1456f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Skinning; +using osuTK.Input; namespace osu.Game.Tests.Visual.Navigation { @@ -79,6 +80,16 @@ namespace osu.Game.Tests.Visual.Navigation [Resolved] private OsuGameBase gameBase { get; set; } + [Test] + public void TestCursorHidesWhenIdle() + { + AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("wait until idle", () => Game.IsIdle.Value); + AddUntilStep("menu cursor hidden", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0); + AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); + AddUntilStep("menu cursor shown", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 1); + } + [Test] public void TestNullRulesetHandled() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index bcbe146456..6c0191ae27 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -81,25 +82,24 @@ namespace osu.Game.Tests.Visual.UserInterface }; AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b)); - - testUserCursor(); - testLocalCursor(); - testUserCursorOverride(); - testMultipleLocalCursors(); } + [SetUp] + public void SetUp() => Schedule(moveOut); + /// /// -- Green Box -- /// Tests whether hovering in and out of a drawable that provides the user cursor (green) /// results in the correct visibility state for that cursor. /// - private void testUserCursor() + [Test] + public void TestUserCursor() { AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); - AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor)); - AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor)); + AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); AddStep("Move out", moveOut); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } @@ -108,15 +108,16 @@ namespace osu.Game.Tests.Visual.UserInterface /// Tests whether hovering in and out of a drawable that provides a local cursor (purple) /// results in the correct visibility and state for that cursor. /// - private void testLocalCursor() + [Test] + public void TestLocalCursor() { AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } @@ -125,47 +126,96 @@ namespace osu.Game.Tests.Visual.UserInterface /// Tests whether overriding a user cursor (green) with another user cursor (blue) /// results in the correct visibility and states for the cursors. /// - private void testUserCursorOverride() + [Test] + public void TestUserCursorOverride() { AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor)); - AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor)); } /// /// -- Yellow-Purple Box Boundary -- /// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time. /// - private void testMultipleLocalCursors() + [Test] + public void TestMultipleLocalCursors() { AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); } /// /// -- Yellow-Blue Box Boundary -- /// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue). /// - private void testUserOverrideWithLocal() + [Test] + public void TestUserOverrideWithLocal() { - AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); + AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10, 0))); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + } + + /// + /// Ensures non-mouse input hides global cursor on a "local cursor" area (which doesn't hide global cursor). + /// + [Test] + public void TestKeyboardLocalCursor([Values] bool clickToShow) + { + AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3].ScreenSpaceDrawQuad.Centre + new Vector2(10, 0))); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddAssert("Check global cursor alpha is 1", () => globalCursorDisplay.MenuCursor.Alpha == 1); + + AddStep("Press key", () => InputManager.Key(Key.A)); + AddAssert("Check purple cursor still visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddUntilStep("Check global cursor alpha is 0", () => globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0); + + if (clickToShow) + AddStep("Click mouse", () => InputManager.Click(MouseButton.Left)); + else + AddStep("Move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + Vector2.One)); + + AddAssert("Check purple cursor still visible", () => checkVisible(cursorBoxes[3].Cursor)); + AddUntilStep("Check global cursor alpha is 1", () => globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 1); + } + + /// + /// Ensures mouse input after non-mouse input doesn't show global cursor on a "user cursor" area (which hides global cursor). + /// + [Test] + public void TestKeyboardUserCursor([Values] bool clickToShow) + { + AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); + AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check global cursor alpha is 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0); + + AddStep("Press key", () => InputManager.Key(Key.A)); + AddAssert("Check green cursor still visible", () => checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check global cursor alpha is still 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0); + + if (clickToShow) + AddStep("Click mouse", () => InputManager.Click(MouseButton.Left)); + else + AddStep("Move mouse", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + Vector2.One)); + + AddAssert("Check green cursor still visible", () => checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check global cursor alpha is still 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0); } /// @@ -191,7 +241,7 @@ namespace osu.Game.Tests.Visual.UserInterface { public bool SmoothTransition; - public CursorContainer MenuCursor { get; } + public CursorContainer Cursor { get; } public bool ProvidingUserCursor { get; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor); @@ -218,7 +268,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = providesUserCursor ? "User cursor" : "Local cursor" }, - MenuCursor = new TestCursorContainer + Cursor = new TestCursorContainer { State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible }, } diff --git a/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs index 6613e18cbe..f5429723be 100644 --- a/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs +++ b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs @@ -13,7 +13,7 @@ using osu.Game.Configuration; namespace osu.Game.Graphics.Cursor { /// - /// A container which provides the main . + /// A container which provides the main . /// Also handles cases where a more localised cursor is provided by another component (via ). /// public class GlobalCursorDisplay : Container, IProvideCursor @@ -23,7 +23,9 @@ namespace osu.Game.Graphics.Cursor /// internal bool ShowCursor = true; - public CursorContainer MenuCursor { get; } + CursorContainer IProvideCursor.Cursor => MenuCursor; + + public MenuCursorContainer MenuCursor { get; } public bool ProvidingUserCursor => true; @@ -42,8 +44,8 @@ namespace osu.Game.Graphics.Cursor { AddRangeInternal(new Drawable[] { - MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } }, - Content = new Container { RelativeSizeAxes = Axes.Both } + Content = new Container { RelativeSizeAxes = Axes.Both }, + MenuCursor = new MenuCursorContainer { State = { Value = Visibility.Hidden } } }); } @@ -64,7 +66,7 @@ namespace osu.Game.Graphics.Cursor if (!hasValidInput || !ShowCursor) { - currentOverrideProvider?.MenuCursor?.Hide(); + currentOverrideProvider?.Cursor?.Hide(); currentOverrideProvider = null; return; } @@ -83,8 +85,8 @@ namespace osu.Game.Graphics.Cursor if (currentOverrideProvider == newOverrideProvider) return; - currentOverrideProvider?.MenuCursor?.Hide(); - newOverrideProvider.MenuCursor?.Show(); + currentOverrideProvider?.Cursor?.Hide(); + newOverrideProvider.Cursor?.Show(); currentOverrideProvider = newOverrideProvider; } diff --git a/osu.Game/Graphics/Cursor/IProvideCursor.cs b/osu.Game/Graphics/Cursor/IProvideCursor.cs index f7f7b75bc8..9f01e5da6d 100644 --- a/osu.Game/Graphics/Cursor/IProvideCursor.cs +++ b/osu.Game/Graphics/Cursor/IProvideCursor.cs @@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor /// The cursor provided by this . /// May be null if no cursor should be visible. /// - CursorContainer MenuCursor { get; } + CursorContainer Cursor { get; } /// - /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. + /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays). /// bool ProvidingUserCursor { get; } diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs similarity index 73% rename from osu.Game/Graphics/Cursor/MenuCursor.cs rename to osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 862a10208c..33b0c308cc 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -1,10 +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 System; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -21,24 +18,28 @@ using osuTK; namespace osu.Game.Graphics.Cursor { - public class MenuCursor : CursorContainer + public class MenuCursorContainer : CursorContainer { private readonly IBindable screenshotCursorVisibility = new Bindable(true); public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent; protected override Drawable CreateCursor() => activeCursor = new Cursor(); - private Cursor activeCursor; + private Cursor activeCursor = null!; - private Bindable cursorRotate; private DragRotationState dragRotationState; private Vector2 positionMouseDown; - - private Sample tapSample; private Vector2 lastMovePosition; - [BackgroundDependencyLoader(true)] - private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) + private Bindable cursorRotate = null!; + private Sample tapSample = null!; + + private MouseInputDetector mouseInputDetector = null!; + + private bool visible; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, ScreenshotManager? screenshotManager, AudioManager audio) { cursorRotate = config.GetBindable(OsuSetting.CursorRotation); @@ -46,6 +47,45 @@ namespace osu.Game.Graphics.Cursor screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); tapSample = audio.Samples.Get(@"UI/cursor-tap"); + + Add(mouseInputDetector = new MouseInputDetector()); + } + + [Resolved] + private OsuGame? game { get; set; } + + private readonly IBindable lastInputWasMouse = new BindableBool(); + private readonly IBindable isIdle = new BindableBool(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + lastInputWasMouse.BindTo(mouseInputDetector.LastInputWasMouseSource); + lastInputWasMouse.BindValueChanged(_ => updateState(), true); + + if (game != null) + { + isIdle.BindTo(game.IsIdle); + isIdle.BindValueChanged(_ => updateState()); + } + } + + protected override void UpdateState(ValueChangedEvent state) => updateState(); + + private void updateState() + { + bool combinedVisibility = State.Value == Visibility.Visible && lastInputWasMouse.Value && !isIdle.Value; + + if (visible == combinedVisibility) + return; + + visible = combinedVisibility; + + if (visible) + PopIn(); + else + PopOut(); } protected override void Update() @@ -163,11 +203,11 @@ namespace osu.Game.Graphics.Cursor public class Cursor : Container { - private Container cursorContainer; - private Bindable cursorScale; + private Container cursorContainer = null!; + private Bindable cursorScale = null!; private const float base_scale = 0.15f; - public Sprite AdditiveLayer; + public Sprite AdditiveLayer = null!; public Cursor() { @@ -204,6 +244,35 @@ namespace osu.Game.Graphics.Cursor } } + private class MouseInputDetector : Component + { + /// + /// Whether the last input applied to the game is sourced from mouse. + /// + public IBindable LastInputWasMouseSource => lastInputWasMouseSource; + + private readonly Bindable lastInputWasMouseSource = new Bindable(); + + public MouseInputDetector() + { + RelativeSizeAxes = Axes.Both; + } + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case MouseEvent: + lastInputWasMouseSource.Value = true; + return false; + + default: + lastInputWasMouseSource.Value = false; + return false; + } + } + } + private enum DragRotationState { NotDragging, diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 7f86a060ad..e51dbeed14 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Localisation; using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Overlays.OSD; @@ -94,15 +93,7 @@ namespace osu.Game.Graphics.UserInterface private void copyUrl() { host.GetClipboard()?.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); - } - - private class CopyUrlToast : Toast - { - public CopyUrlToast(LocalisableString value) - : base(UserInterfaceStrings.GeneralHeader, value, "") - { - } + onScreenDisplay?.Display(new CopyUrlToast()); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1716e48395..b3eaf5cd01 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -70,6 +70,7 @@ namespace osu.Game /// The full osu! experience. Builds on top of to add menus and binding logic /// for initial components that are generally retrieved via DI. /// + [Cached(typeof(OsuGame))] public class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { /// @@ -136,6 +137,11 @@ namespace osu.Game private IdleTracker idleTracker; + /// + /// Whether the user is currently in an idle state. + /// + public IBindable IsIdle => idleTracker.IsIdle; + /// /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. /// @@ -266,8 +272,6 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { - dependencies.CacheAs(this); - SentryLogger.AttachUser(API.LocalUser); dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 478f154d58..7d9ed7bf3e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -62,6 +62,7 @@ namespace osu.Game /// Unlike , this class will not load any kind of UI, allowing it to be used /// for provide dependencies to test cases without interfering with them. /// + [Cached(typeof(OsuGameBase))] public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" }; @@ -253,7 +254,6 @@ namespace osu.Game largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore())); dependencies.Cache(largeStore); - dependencies.CacheAs(this); dependencies.CacheAs(LocalConfig); InitialiseFonts(); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 6182f9b188..75f68782b8 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -22,6 +22,7 @@ using System.Collections.Specialized; using osu.Framework.Extensions; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Platform; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Online.API; @@ -75,6 +76,9 @@ namespace osu.Game.Overlays.Comments [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private GameHost host { get; set; } = null!; + [Resolved(canBeNull: true)] private OnScreenDisplay? onScreenDisplay { get; set; } @@ -210,7 +214,6 @@ namespace osu.Game.Overlays.Comments { Name = @"Actions buttons", AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10, 0) }, actionsLoading = new LoadingSpinner { @@ -330,6 +333,9 @@ namespace osu.Game.Overlays.Comments if (WasDeleted) makeDeleted(); + actionsContainer.AddLink("Copy link", copyUrl); + actionsContainer.AddArbitraryDrawable(new Container { Width = 10 }); + if (Comment.UserId.HasValue && Comment.UserId.Value == api.LocalUser.Value.Id) actionsContainer.AddLink("Delete", deleteComment); else @@ -428,6 +434,12 @@ namespace osu.Game.Overlays.Comments api.Queue(request); } + private void copyUrl() + { + host.GetClipboard()?.SetText($@"{api.APIEndpointUrl}/comments/{Comment.Id}"); + onScreenDisplay?.Display(new CopyUrlToast()); + } + protected override void LoadComplete() { ShowDeleted.BindValueChanged(show => diff --git a/osu.Game/Overlays/OSD/CopyUrlToast.cs b/osu.Game/Overlays/OSD/CopyUrlToast.cs new file mode 100644 index 0000000000..ea835a1c5e --- /dev/null +++ b/osu.Game/Overlays/OSD/CopyUrlToast.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Localisation; + +namespace osu.Game.Overlays.OSD +{ + public class CopyUrlToast : Toast + { + public CopyUrlToast() + : base(UserInterfaceStrings.GeneralHeader, ToastStrings.UrlCopied, "") + { + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 2fea2e34b2..c91a6a48d4 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -327,6 +327,50 @@ namespace osu.Game.Overlays.Settings.Sections.Input finalise(); } + protected override bool OnTabletAuxiliaryButtonPress(TabletAuxiliaryButtonPressEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnTabletAuxiliaryButtonRelease(TabletAuxiliaryButtonReleaseEvent e) + { + if (!HasFocus) + { + base.OnTabletAuxiliaryButtonRelease(e); + return; + } + + finalise(); + } + + protected override bool OnTabletPenButtonPress(TabletPenButtonPressEvent e) + { + if (!HasFocus) + return false; + + bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); + finalise(); + + return true; + } + + protected override void OnTabletPenButtonRelease(TabletPenButtonReleaseEvent e) + { + if (!HasFocus) + { + base.OnTabletPenButtonRelease(e); + return; + } + + finalise(); + } + private void clear() { if (bindTarget == null) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 73acb1759f..dd3a950264 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -384,7 +384,7 @@ namespace osu.Game.Rulesets.UI // only show the cursor when within the playfield, by default. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos); - CursorContainer IProvideCursor.MenuCursor => Playfield.Cursor; + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; public override GameplayCursorContainer Cursor => Playfield.Cursor; @@ -499,6 +499,7 @@ namespace osu.Game.Rulesets.UI /// /// The cursor being displayed by the . May be null if no cursor is provided. /// + [CanBeNull] public abstract GameplayCursorContainer Cursor { get; } /// diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 2ec72d8fe3..e59e45722a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -202,16 +202,14 @@ namespace osu.Game.Rulesets.UI /// /// The cursor currently being used by this . May be null if no cursor is provided. /// + [CanBeNull] public GameplayCursorContainer Cursor { get; private set; } /// /// Provide a cursor which is to be used for gameplay. /// - /// - /// The default provided cursor is invisible when inside the bounds of the . - /// /// The cursor, or null to show the menu cursor. - protected virtual GameplayCursorContainer CreateCursor() => new InvisibleCursorContainer(); + protected virtual GameplayCursorContainer CreateCursor() => null; /// /// Registers a as a nested . @@ -522,14 +520,5 @@ namespace osu.Game.Rulesets.UI } #endregion - - public class InvisibleCursorContainer : GameplayCursorContainer - { - protected override Drawable CreateCursor() => new InvisibleCursor(); - - private class InvisibleCursor : Drawable - { - } - } } } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 747f4d4a8a..d6b9c62369 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -44,8 +45,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public Bindable LabelStyle { get; } = new Bindable(LabelStyles.Icons); private SpriteIcon arrow; - private Drawable labelEarly; - private Drawable labelLate; + private UprightAspectMaintainingContainer labelEarly; + private UprightAspectMaintainingContainer labelLate; private Container colourBarsEarly; private Container colourBarsLate; @@ -122,6 +123,20 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Y, Width = judgement_line_width, }, + labelEarly = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Y = -10, + }, + labelLate = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Y = 10, + }, } }, arrowContainer = new Container @@ -261,57 +276,39 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { const float icon_size = 14; - labelEarly?.Expire(); - labelEarly = null; - - labelLate?.Expire(); - labelLate = null; - switch (style) { case LabelStyles.None: break; case LabelStyles.Icons: - labelEarly = new SpriteIcon + labelEarly.Child = new SpriteIcon { - Y = -10, Size = new Vector2(icon_size), Icon = FontAwesome.Solid.ShippingFast, - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, }; - labelLate = new SpriteIcon + labelLate.Child = new SpriteIcon { - Y = 10, Size = new Vector2(icon_size), Icon = FontAwesome.Solid.Bicycle, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, }; break; case LabelStyles.Text: - labelEarly = new OsuSpriteText + labelEarly.Child = new OsuSpriteText { - Y = -10, Text = "Early", Font = OsuFont.Default.With(size: 10), Height = 12, - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, }; - labelLate = new OsuSpriteText + labelLate.Child = new OsuSpriteText { - Y = 10, Text = "Late", Font = OsuFont.Default.With(size: 10), Height = 12, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, }; break; @@ -320,26 +317,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters throw new ArgumentOutOfRangeException(nameof(style), style, null); } - if (labelEarly != null) - { - colourBars.Add(labelEarly); - labelEarly.FadeInFromZero(500); - } - - if (labelLate != null) - { - colourBars.Add(labelLate); - labelLate.FadeInFromZero(500); - } - } - - protected override void Update() - { - base.Update(); - - // undo any layout rotation to display icons in the correct orientation - if (labelEarly != null) labelEarly.Rotation = -Rotation; - if (labelLate != null) labelLate.Rotation = -Rotation; + labelEarly.FadeInFromZero(500); + labelLate.FadeInFromZero(500); } private void createColourBars((HitResult result, double length)[] windows) diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index c8e0bf93a2..b7d45ba642 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility public readonly Bindable VisualMode = new Bindable(); - public CursorContainer? MenuCursor { get; private set; } + public CursorContainer? Cursor { get; private set; } public bool ProvidingUserCursor => IsActiveArea.Value; @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - MenuCursor = new LatencyCursorContainer + Cursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - MenuCursor = new LatencyCursorContainer + Cursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - MenuCursor = new LatencyCursorContainer + Cursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index e47d19fba6..3ca83a4781 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -17,6 +17,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; @@ -42,6 +43,8 @@ namespace osu.Game.Tests.Visual protected override bool CreateNestedActionContainer => false; + protected override bool DisplayCursorForManualInput => false; + [BackgroundDependencyLoader] private void load() { @@ -119,6 +122,8 @@ namespace osu.Game.Tests.Visual public RealmAccess Realm => Dependencies.Get(); + public new GlobalCursorDisplay GlobalCursorDisplay => base.GlobalCursorDisplay; + public new BackButton BackButton => base.BackButton; public new BeatmapManager BeatmapManager => base.BeatmapManager; diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 9082ca9c58..e56c546bac 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -36,21 +36,31 @@ namespace osu.Game.Tests.Visual /// protected virtual bool CreateNestedActionContainer => true; + /// + /// Whether a menu cursor controlled by the manual input manager should be displayed. + /// True by default, but is disabled for s as they provide their own global cursor. + /// + protected virtual bool DisplayCursorForManualInput => true; + protected OsuManualInputManagerTestScene() { - GlobalCursorDisplay cursorDisplay; + var mainContent = content = new Container { RelativeSizeAxes = Axes.Both }; - CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; - - cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor) + if (DisplayCursorForManualInput) { - RelativeSizeAxes = Axes.Both - }; + var cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; + + cursorDisplay.Add(new OsuTooltipContainer(cursorDisplay.MenuCursor) + { + RelativeSizeAxes = Axes.Both, + Child = mainContent + }); + + mainContent = cursorDisplay; + } if (CreateNestedActionContainer) - { mainContent = new GlobalActionContainer(null).WithChild(mainContent); - } base.Content.AddRange(new Drawable[] {