1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 04:13:00 +08:00

Merge branch 'master' into comment-report

This commit is contained in:
Feodor0090 2022-10-17 16:08:13 +03:00 committed by GitHub
commit 6efe7b5334
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 366 additions and 159 deletions

View File

@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override LocalisableString Description => @"Use the mouse to control the catcher.";
private DrawableRuleset<CatchHitObject> drawableRuleset = null!;
private DrawableCatchRuleset drawableRuleset = null!;
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> 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<CatchAction>, 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;
}

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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;
}
}
}

View File

@ -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)
{

View File

@ -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
{

View File

@ -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<TexturedVertex2D>(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)
{

View File

@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Osu.UI
private readonly ProxyContainer spinnerProxies;
private readonly JudgementContainer<DrawableOsuJudgement> 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<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },

View File

@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Editing
}
}
},
new MenuCursor()
new MenuCursorContainer()
};
scrollContainer.Add(innerBox = new Box

View File

@ -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()
{

View File

@ -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);
/// <summary>
/// -- 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.
/// </summary>
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.
/// </summary>
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.
/// </summary>
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));
}
/// <summary>
/// -- Yellow-Purple Box Boundary --
/// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time.
/// </summary>
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));
}
/// <summary>
/// -- Yellow-Blue Box Boundary --
/// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue).
/// </summary>
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));
}
/// <summary>
/// Ensures non-mouse input hides global cursor on a "local cursor" area (which doesn't hide global cursor).
/// </summary>
[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);
}
/// <summary>
/// Ensures mouse input after non-mouse input doesn't show global cursor on a "user cursor" area (which hides global cursor).
/// </summary>
[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);
}
/// <summary>
@ -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 },
}

View File

@ -13,7 +13,7 @@ using osu.Game.Configuration;
namespace osu.Game.Graphics.Cursor
{
/// <summary>
/// A container which provides the main <see cref="Cursor.MenuCursor"/>.
/// A container which provides the main <see cref="MenuCursorContainer"/>.
/// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
/// </summary>
public class GlobalCursorDisplay : Container, IProvideCursor
@ -23,7 +23,9 @@ namespace osu.Game.Graphics.Cursor
/// </summary>
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;
}

View File

@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor
/// The cursor provided by this <see cref="IDrawable"/>.
/// May be null if no cursor should be visible.
/// </summary>
CursorContainer MenuCursor { get; }
CursorContainer Cursor { get; }
/// <summary>
/// Whether <see cref="MenuCursor"/> should be displayed as the singular user cursor. This will temporarily hide any other user cursor.
/// Whether <see cref="Cursor"/> 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).
/// </summary>
bool ProvidingUserCursor { get; }

View File

@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#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<bool> screenshotCursorVisibility = new Bindable<bool>(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<bool> 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<bool> 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<bool>(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<bool> lastInputWasMouse = new BindableBool();
private readonly IBindable<bool> 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<Visibility> 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<float> cursorScale;
private Container cursorContainer = null!;
private Bindable<float> 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
{
/// <summary>
/// Whether the last input applied to the game is sourced from mouse.
/// </summary>
public IBindable<bool> LastInputWasMouseSource => lastInputWasMouseSource;
private readonly Bindable<bool> lastInputWasMouseSource = new Bindable<bool>();
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,

View File

@ -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());
}
}
}

View File

@ -70,6 +70,7 @@ namespace osu.Game
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
/// for initial components that are generally retrieved via DI.
/// </summary>
[Cached(typeof(OsuGame))]
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
{
/// <summary>
@ -136,6 +137,11 @@ namespace osu.Game
private IdleTracker idleTracker;
/// <summary>
/// Whether the user is currently in an idle state.
/// </summary>
public IBindable<bool> IsIdle => idleTracker.IsIdle;
/// <summary>
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
/// </summary>
@ -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 });

View File

@ -62,6 +62,7 @@ namespace osu.Game
/// Unlike <see cref="OsuGame"/>, this class will not load any kind of UI, allowing it to be used
/// for provide dependencies to test cases without interfering with them.
/// </summary>
[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();

View File

@ -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 =>

View File

@ -0,0 +1,15 @@
// 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.Game.Localisation;
namespace osu.Game.Overlays.OSD
{
public class CopyUrlToast : Toast
{
public CopyUrlToast()
: base(UserInterfaceStrings.GeneralHeader, ToastStrings.UrlCopied, "")
{
}
}
}

View File

@ -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)

View File

@ -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
/// <summary>
/// The cursor being displayed by the <see cref="Playfield"/>. May be null if no cursor is provided.
/// </summary>
[CanBeNull]
public abstract GameplayCursorContainer Cursor { get; }
/// <summary>

View File

@ -202,16 +202,14 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
/// </summary>
[CanBeNull]
public GameplayCursorContainer Cursor { get; private set; }
/// <summary>
/// Provide a cursor which is to be used for gameplay.
/// </summary>
/// <remarks>
/// The default provided cursor is invisible when inside the bounds of the <see cref="Playfield"/>.
/// </remarks>
/// <returns>The cursor, or null to show the menu cursor.</returns>
protected virtual GameplayCursorContainer CreateCursor() => new InvisibleCursorContainer();
protected virtual GameplayCursorContainer CreateCursor() => null;
/// <summary>
/// Registers a <see cref="Playfield"/> as a nested <see cref="Playfield"/>.
@ -522,14 +520,5 @@ namespace osu.Game.Rulesets.UI
}
#endregion
public class InvisibleCursorContainer : GameplayCursorContainer
{
protected override Drawable CreateCursor() => new InvisibleCursor();
private class InvisibleCursor : Drawable
{
}
}
}
}

View File

@ -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<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(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)

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility
public readonly Bindable<LatencyVisualMode> VisualMode = new Bindable<LatencyVisualMode>();
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,
},

View File

@ -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<RealmAccess>();
public new GlobalCursorDisplay GlobalCursorDisplay => base.GlobalCursorDisplay;
public new BackButton BackButton => base.BackButton;
public new BeatmapManager BeatmapManager => base.BeatmapManager;

View File

@ -36,21 +36,31 @@ namespace osu.Game.Tests.Visual
/// </summary>
protected virtual bool CreateNestedActionContainer => true;
/// <summary>
/// Whether a menu cursor controlled by the manual input manager should be displayed.
/// True by default, but is disabled for <see cref="OsuGameTestScene"/>s as they provide their own global cursor.
/// </summary>
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[]
{