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

Merge branch 'master' into remove-current-item

This commit is contained in:
Dean Herbert 2022-10-20 10:02:09 +09:00 committed by GitHub
commit 2f8a4fd2d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 688 additions and 299 deletions

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.Edit
{
public class CatchEditorPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
{
protected override Container<Drawable> Content => content;
private readonly Container content;
public CatchEditorPlayfieldAdjustmentContainer()
{
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
Size = new Vector2(0.8f, 0.9f);
InternalChild = new ScalingContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Child = content = new Container { RelativeSizeAxes = Axes.Both },
};
}
private class ScalingContainer : Container
{
public ScalingContainer()
{
RelativeSizeAxes = Axes.Y;
Width = CatchPlayfield.WIDTH;
}
protected override void Update()
{
base.Update();
Scale = new Vector2(Math.Min(Parent.ChildSize.X / CatchPlayfield.WIDTH, Parent.ChildSize.Y / CatchPlayfield.HEIGHT));
Height = 1 / Scale.Y;
}
}
}
}

View File

@ -12,8 +12,10 @@ using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
@ -37,6 +39,12 @@ namespace osu.Game.Rulesets.Catch.Edit
private InputManager inputManager; private InputManager inputManager;
private readonly BindableDouble timeRangeMultiplier = new BindableDouble(1)
{
MinValue = 1,
MaxValue = 10,
};
public CatchHitObjectComposer(CatchRuleset ruleset) public CatchHitObjectComposer(CatchRuleset ruleset)
: base(ruleset) : base(ruleset)
{ {
@ -51,7 +59,10 @@ namespace osu.Game.Rulesets.Catch.Edit
LayerBelowRuleset.Add(new PlayfieldBorder LayerBelowRuleset.Add(new PlayfieldBorder
{ {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = CatchPlayfield.HEIGHT,
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
}); });
@ -77,8 +88,30 @@ namespace osu.Game.Rulesets.Catch.Edit
updateDistanceSnapGrid(); updateDistanceSnapGrid();
} }
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
{
// Note that right now these are hard to use as the default key bindings conflict with existing editor key bindings.
// In the future we will want to expose this via UI and potentially change the key bindings to be editor-specific.
// May be worth considering standardising "zoom" behaviour with what the timeline uses (ie. alt-wheel) but that may cause new conflicts.
case GlobalAction.IncreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value - 1, 200, Easing.OutQuint);
break;
case GlobalAction.DecreaseScrollSpeed:
this.TransformBindableTo(timeRangeMultiplier, timeRangeMultiplier.Value + 1, 200, Easing.OutQuint);
break;
}
return base.OnPressed(e);
}
protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => protected override DrawableRuleset<CatchHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) =>
new DrawableCatchEditorRuleset(ruleset, beatmap, mods); new DrawableCatchEditorRuleset(ruleset, beatmap, mods)
{
TimeRangeMultiplier = { BindTarget = timeRangeMultiplier, }
};
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{ {

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -13,11 +14,24 @@ namespace osu.Game.Rulesets.Catch.Edit
{ {
public class DrawableCatchEditorRuleset : DrawableCatchRuleset public class DrawableCatchEditorRuleset : DrawableCatchRuleset
{ {
public readonly BindableDouble TimeRangeMultiplier = new BindableDouble(1);
public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null) public DrawableCatchEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
{ {
} }
protected override void Update()
{
base.Update();
double gamePlayTimeRange = GetTimeRange(Beatmap.Difficulty.ApproachRate);
float playfieldStretch = Playfield.DrawHeight / CatchPlayfield.HEIGHT;
TimeRange.Value = gamePlayTimeRange * TimeRangeMultiplier.Value * playfieldStretch;
}
protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty); protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchEditorPlayfieldAdjustmentContainer();
} }
} }

View File

@ -19,17 +19,20 @@ namespace osu.Game.Rulesets.Catch.Mods
{ {
public override LocalisableString Description => @"Use the mouse to control the catcher."; 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) public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
{ {
this.drawableRuleset = drawableRuleset; this.drawableRuleset = (DrawableCatchRuleset)drawableRuleset;
} }
public void ApplyToPlayer(Player player) public void ApplyToPlayer(Player player)
{ {
if (!drawableRuleset.HasReplayLoaded.Value) 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 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 override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public MouseInputHelper(CatchPlayfield playfield) public MouseInputHelper(CatcherArea catcherArea)
{ {
catcherArea = playfield.CatcherArea; this.catcherArea = catcherArea;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }

View File

@ -23,6 +23,12 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
public const float WIDTH = 512; public const float WIDTH = 512;
/// <summary>
/// The height of the playfield.
/// This doesn't include the catcher area.
/// </summary>
public const float HEIGHT = 384;
/// <summary> /// <summary>
/// The center position of the playfield. /// The center position of the playfield.
/// </summary> /// </summary>

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
{ {
Direction.Value = ScrollingDirection.Down; Direction.Value = ScrollingDirection.Down;
TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); TimeRange.Value = GetTimeRange(beatmap.Difficulty.ApproachRate);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.UI
KeyBindingInputManager.Add(new CatchTouchInputMapper()); KeyBindingInputManager.Add(new CatchTouchInputMapper());
} }
protected double GetTimeRange(float approachRate) => IBeatmapDifficultyInfo.DifficultyRange(approachRate, 1800, 1200, 450);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
@ -33,6 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
[Cached] [Cached]
private readonly EditorClock editorClock; private readonly EditorClock editorClock;

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods 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 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

@ -60,6 +60,9 @@ namespace osu.Game.Rulesets.Osu.Edit
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
// Give a bit of breathing room around the playfield content.
PlayfieldContentContainer.Padding = new MarginPadding(10);
LayerBelowRuleset.AddRange(new Drawable[] LayerBelowRuleset.AddRange(new Drawable[]
{ {
distanceSnapGridContainer = new Container distanceSnapGridContainer = new Container

View File

@ -3,6 +3,7 @@
using System; using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield) 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) 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. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Localisation; using osu.Framework.Localisation;
@ -9,6 +10,7 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Utils; using osu.Game.Utils;
@ -33,9 +35,15 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield) 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; 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 System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -45,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void Update(Playfield playfield) 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) foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
{ {

View File

@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdateHitStateTransforms(state); base.UpdateHitStateTransforms(state);
const float fade_out_time = 450; const float fade_out_time = 240;
switch (state) switch (state)
{ {
@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break; break;
} }
this.FadeOut(fade_out_time, Easing.OutQuint).Expire(); this.FadeOut(fade_out_time).Expire();
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// </summary> /// </summary>
public readonly IBindable<double> SpinsPerMinute = new BindableDouble(); public readonly IBindable<double> SpinsPerMinute = new BindableDouble();
private const double fade_out_duration = 160; private const double fade_out_duration = 240;
public DrawableSpinner() public DrawableSpinner()
: this(null) : this(null)

View File

@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{ {
default: default:
JudgementText JudgementText
.FadeInFromZero(300, Easing.OutQuint)
.ScaleTo(Vector2.One) .ScaleTo(Vector2.One)
.ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint); .ScaleTo(new Vector2(1.2f), 1800, Easing.OutQuint);
break; break;
@ -96,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
ringExplosion?.PlayAnimation(); ringExplosion?.PlayAnimation();
} }
public Drawable? GetAboveHitObjectsProxiedContent() => null; public Drawable? GetAboveHitObjectsProxiedContent() => JudgementText.CreateProxy();
private class RingExplosion : CompositeDrawable private class RingExplosion : CompositeDrawable
{ {

View File

@ -134,10 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
switch (state) switch (state)
{ {
case ArmedState.Hit: case ArmedState.Hit:
CircleSprite.FadeOut(legacy_fade_duration, Easing.Out); CircleSprite.FadeOut(legacy_fade_duration);
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
OverlaySprite.FadeOut(legacy_fade_duration, Easing.Out); OverlaySprite.FadeOut(legacy_fade_duration);
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber) if (hasNumber)
@ -146,11 +146,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
if (legacyVersion >= 2.0m) if (legacyVersion >= 2.0m)
// legacy skins of version 2.0 and newer only apply very short fade out to the number piece. // legacy skins of version 2.0 and newer only apply very short fade out to the number piece.
hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); hitCircleText.FadeOut(legacy_fade_duration / 4);
else else
{ {
// old skins scale and fade it normally along other pieces. // old skins scale and fade it normally along other pieces.
hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); hitCircleText.FadeOut(legacy_fade_duration);
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
} }
} }

View File

@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
this.FadeOut(); this.FadeOut();
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
this.FadeInFromZero(spinner.TimeFadeIn / 2); this.FadeInFromZero(spinner.TimeFadeIn);
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt)) using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{ {

View File

@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spin = new Sprite spin = new Sprite
{ {
Alpha = 0,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-spin"), Texture = source.GetTexture("spinner-spin"),
@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}, },
bonusCounter = new LegacySpriteText(LegacyFont.Score) bonusCounter = new LegacySpriteText(LegacyFont.Score)
{ {
Alpha = 0f, Alpha = 0,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
@ -179,6 +180,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out); spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
} }
using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn / 2))
spin.FadeInFromZero(d.HitObject.TimeFadeIn / 2);
using (BeginAbsoluteSequence(d.HitObject.StartTime)) using (BeginAbsoluteSequence(d.HitObject.StartTime))
ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration); ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration);

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Rendering.Vertices;
using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shaders;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Utils;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -185,6 +186,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
private float radius; private float radius;
private Vector2 drawSize; private Vector2 drawSize;
private Texture? texture; private Texture? texture;
private int rotationSeed;
private int rotationIndex;
// anim calculation vars (color, scale, direction) // anim calculation vars (color, scale, direction)
private double initialFadeOutDurationTrunc; private double initialFadeOutDurationTrunc;
@ -194,8 +197,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
private double reFadeInTime; private double reFadeInTime;
private double finalFadeOutTime; private double finalFadeOutTime;
private Random rotationRNG = new Random();
public SmokeDrawNode(ITexturedShaderDrawable source) public SmokeDrawNode(ITexturedShaderDrawable source)
: base(source) : base(source)
{ {
@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
SmokeEndTime = Source.smokeEndTime; SmokeEndTime = Source.smokeEndTime;
CurrentTime = Source.Clock.CurrentTime; CurrentTime = Source.Clock.CurrentTime;
rotationRNG = new Random(Source.rotationSeed); rotationSeed = Source.rotationSeed;
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime); initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc; firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
@ -233,6 +234,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
if (points.Count == 0) if (points.Count == 0)
return; return;
rotationIndex = 0;
quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10); quadBatch ??= renderer.CreateQuadBatch<TexturedVertex2D>(max_point_count / 10, 10);
texture ??= renderer.WhitePixel; texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect(); RectangleF textureRect = texture.GetTextureRect();
@ -311,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle)); 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) 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 ProxyContainer spinnerProxies;
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer; private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
public SmokeContainer Smoke { get; }
public FollowPointRenderer FollowPoints { get; } public FollowPointRenderer FollowPoints { get; }
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.UI
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
new SmokeContainer { RelativeSizeAxes = Axes.Both }, Smoke = new SmokeContainer { RelativeSizeAxes = Axes.Both },
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },

View File

@ -244,7 +244,10 @@ namespace osu.Game.Tests.Visual.Background
public void TestResumeFromPlayer() public void TestResumeFromPlayer()
{ {
performFullSetup(); performFullSetup();
AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); AddStep("Move mouse to Visual Settings location", () => InputManager.MoveMouseTo(playerLoader.ScreenSpaceDrawQuad.TopRight
+ new Vector2(-playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Width,
playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Height / 2
)));
AddStep("Resume PlayerLoader", () => player.Restart()); AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));

View File

@ -4,8 +4,10 @@
#nullable disable #nullable disable
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Components.RadioButtons;
namespace osu.Game.Tests.Visual.Editing namespace osu.Game.Tests.Visual.Editing
@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture] [TestFixture]
public class TestSceneEditorComposeRadioButtons : OsuTestScene public class TestSceneEditorComposeRadioButtons : OsuTestScene
{ {
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
public TestSceneEditorComposeRadioButtons() public TestSceneEditorComposeRadioButtons()
{ {
EditorRadioButtonCollection collection; EditorRadioButtonCollection collection;

View File

@ -148,10 +148,6 @@ namespace osu.Game.Tests.Visual.Editing
}); });
AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0); AddAssert("no circles placed", () => editorBeatmap.HitObjects.Count == 0);
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("circle placed", () => editorBeatmap.HitObjects.Count == 1);
} }
[Test] [Test]

View File

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

View File

@ -25,6 +25,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Navigation namespace osu.Game.Tests.Visual.Navigation
{ {
@ -79,6 +80,16 @@ namespace osu.Game.Tests.Visual.Navigation
[Resolved] [Resolved]
private OsuGameBase gameBase { get; set; } 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] [Test]
public void TestNullRulesetHandled() public void TestNullRulesetHandled()
{ {

View File

@ -15,6 +15,7 @@ using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface 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)); AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b));
testUserCursor();
testLocalCursor();
testUserCursorOverride();
testMultipleLocalCursors();
} }
[SetUp]
public void SetUp() => Schedule(moveOut);
/// <summary> /// <summary>
/// -- Green Box -- /// -- Green Box --
/// Tests whether hovering in and out of a drawable that provides the user cursor (green) /// Tests whether hovering in and out of a drawable that provides the user cursor (green)
/// results in the correct visibility state for that cursor. /// results in the correct visibility state for that cursor.
/// </summary> /// </summary>
private void testUserCursor() [Test]
public void TestUserCursor()
{ {
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor)); AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor)); AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
AddStep("Move out", moveOut); 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)); 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) /// 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. /// results in the correct visibility and state for that cursor.
/// </summary> /// </summary>
private void testLocalCursor() [Test]
public void TestLocalCursor()
{ {
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor));
AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor)); AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor));
AddStep("Move out", moveOut); 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)); 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) /// Tests whether overriding a user cursor (green) with another user cursor (blue)
/// results in the correct visibility and states for the cursors. /// results in the correct visibility and states for the cursors.
/// </summary> /// </summary>
private void testUserCursorOverride() [Test]
public void TestUserCursorOverride()
{ {
AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); 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 blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
AddStep("Move out", moveOut); AddStep("Move out", moveOut);
AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor)); AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor)); AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor));
} }
/// <summary> /// <summary>
/// -- Yellow-Purple Box Boundary -- /// -- Yellow-Purple Box Boundary --
/// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time. /// Tests whether multiple local cursors (purple + yellow) may be visible and at the mouse position at the same time.
/// </summary> /// </summary>
private void testMultipleLocalCursors() [Test]
public void TestMultipleLocalCursors()
{ {
AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); 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 visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
AddStep("Move out", moveOut); AddStep("Move out", moveOut);
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
} }
/// <summary> /// <summary>
/// -- Yellow-Blue Box Boundary -- /// -- Yellow-Blue Box Boundary --
/// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue). /// Tests whether a local cursor (yellow) may be displayed along with a user cursor override (blue).
/// </summary> /// </summary>
private void testUserOverrideWithLocal() [Test]
public void TestUserOverrideWithLocal()
{ {
AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10, 0)));
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor));
AddStep("Move out", moveOut); AddStep("Move out", moveOut);
AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor)); AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); 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> /// <summary>
@ -191,7 +241,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
public bool SmoothTransition; public bool SmoothTransition;
public CursorContainer MenuCursor { get; } public CursorContainer Cursor { get; }
public bool ProvidingUserCursor { get; } public bool ProvidingUserCursor { get; }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor);
@ -218,7 +268,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = providesUserCursor ? "User cursor" : "Local cursor" Text = providesUserCursor ? "User cursor" : "Local cursor"
}, },
MenuCursor = new TestCursorContainer Cursor = new TestCursorContainer
{ {
State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible }, State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible },
} }

View File

@ -5,12 +5,14 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osuTK;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
@ -20,11 +22,17 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
private SettingsToolboxGroup group; private SettingsToolboxGroup group;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
Child = group = new SettingsToolboxGroup("example") Child = group = new SettingsToolboxGroup("example")
{ {
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
new RoundedButton new RoundedButton

View File

@ -13,7 +13,7 @@ using osu.Game.Configuration;
namespace osu.Game.Graphics.Cursor namespace osu.Game.Graphics.Cursor
{ {
/// <summary> /// <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"/>). /// Also handles cases where a more localised cursor is provided by another component (via <see cref="IProvideCursor"/>).
/// </summary> /// </summary>
public class GlobalCursorDisplay : Container, IProvideCursor public class GlobalCursorDisplay : Container, IProvideCursor
@ -23,7 +23,9 @@ namespace osu.Game.Graphics.Cursor
/// </summary> /// </summary>
internal bool ShowCursor = true; internal bool ShowCursor = true;
public CursorContainer MenuCursor { get; } CursorContainer IProvideCursor.Cursor => MenuCursor;
public MenuCursorContainer MenuCursor { get; }
public bool ProvidingUserCursor => true; public bool ProvidingUserCursor => true;
@ -42,8 +44,8 @@ namespace osu.Game.Graphics.Cursor
{ {
AddRangeInternal(new Drawable[] 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) if (!hasValidInput || !ShowCursor)
{ {
currentOverrideProvider?.MenuCursor?.Hide(); currentOverrideProvider?.Cursor?.Hide();
currentOverrideProvider = null; currentOverrideProvider = null;
return; return;
} }
@ -83,8 +85,8 @@ namespace osu.Game.Graphics.Cursor
if (currentOverrideProvider == newOverrideProvider) if (currentOverrideProvider == newOverrideProvider)
return; return;
currentOverrideProvider?.MenuCursor?.Hide(); currentOverrideProvider?.Cursor?.Hide();
newOverrideProvider.MenuCursor?.Show(); newOverrideProvider.Cursor?.Show();
currentOverrideProvider = newOverrideProvider; currentOverrideProvider = newOverrideProvider;
} }

View File

@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor
/// The cursor provided by this <see cref="IDrawable"/>. /// The cursor provided by this <see cref="IDrawable"/>.
/// May be null if no cursor should be visible. /// May be null if no cursor should be visible.
/// </summary> /// </summary>
CursorContainer MenuCursor { get; } CursorContainer Cursor { get; }
/// <summary> /// <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). /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays).
/// </summary> /// </summary>
bool ProvidingUserCursor { get; } bool ProvidingUserCursor { get; }

View File

@ -1,10 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -21,24 +18,28 @@ using osuTK;
namespace osu.Game.Graphics.Cursor namespace osu.Game.Graphics.Cursor
{ {
public class MenuCursor : CursorContainer public class MenuCursorContainer : CursorContainer
{ {
private readonly IBindable<bool> screenshotCursorVisibility = new Bindable<bool>(true); private readonly IBindable<bool> screenshotCursorVisibility = new Bindable<bool>(true);
public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent; public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent;
protected override Drawable CreateCursor() => activeCursor = new Cursor(); protected override Drawable CreateCursor() => activeCursor = new Cursor();
private Cursor activeCursor; private Cursor activeCursor = null!;
private Bindable<bool> cursorRotate;
private DragRotationState dragRotationState; private DragRotationState dragRotationState;
private Vector2 positionMouseDown; private Vector2 positionMouseDown;
private Sample tapSample;
private Vector2 lastMovePosition; private Vector2 lastMovePosition;
[BackgroundDependencyLoader(true)] private Bindable<bool> cursorRotate = null!;
private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) 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); cursorRotate = config.GetBindable<bool>(OsuSetting.CursorRotation);
@ -46,6 +47,45 @@ namespace osu.Game.Graphics.Cursor
screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility);
tapSample = audio.Samples.Get(@"UI/cursor-tap"); 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() protected override void Update()
@ -163,11 +203,11 @@ namespace osu.Game.Graphics.Cursor
public class Cursor : Container public class Cursor : Container
{ {
private Container cursorContainer; private Container cursorContainer = null!;
private Bindable<float> cursorScale; private Bindable<float> cursorScale = null!;
private const float base_scale = 0.15f; private const float base_scale = 0.15f;
public Sprite AdditiveLayer; public Sprite AdditiveLayer = null!;
public Cursor() 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 private enum DragRotationState
{ {
NotDragging, NotDragging,

View File

@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.OSD; using osu.Game.Overlays.OSD;
@ -94,15 +93,7 @@ namespace osu.Game.Graphics.UserInterface
private void copyUrl() private void copyUrl()
{ {
host.GetClipboard()?.SetText(Link); host.GetClipboard()?.SetText(Link);
onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); onScreenDisplay?.Display(new CopyUrlToast());
}
private class CopyUrlToast : Toast
{
public CopyUrlToast(LocalisableString value)
: base(UserInterfaceStrings.GeneralHeader, value, "")
{
}
} }
} }
} }

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 /// 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. /// for initial components that are generally retrieved via DI.
/// </summary> /// </summary>
[Cached(typeof(OsuGame))]
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler
{ {
/// <summary> /// <summary>
@ -136,6 +137,11 @@ namespace osu.Game
private IdleTracker idleTracker; private IdleTracker idleTracker;
/// <summary>
/// Whether the user is currently in an idle state.
/// </summary>
public IBindable<bool> IsIdle => idleTracker.IsIdle;
/// <summary> /// <summary>
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen. /// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
/// </summary> /// </summary>
@ -266,8 +272,6 @@ namespace osu.Game
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
dependencies.CacheAs(this);
SentryLogger.AttachUser(API.LocalUser); SentryLogger.AttachUser(API.LocalUser);
dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); 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 /// 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. /// for provide dependencies to test cases without interfering with them.
/// </summary> /// </summary>
[Cached(typeof(OsuGameBase))]
public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider
{ {
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" }; public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
@ -253,7 +254,6 @@ namespace osu.Game
largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore())); largeStore.AddTextureSource(Host.CreateTextureLoaderStore(new OnlineStore()));
dependencies.Cache(largeStore); dependencies.Cache(largeStore);
dependencies.CacheAs(this);
dependencies.CacheAs(LocalConfig); dependencies.CacheAs(LocalConfig);
InitialiseFonts(); InitialiseFonts();

View File

@ -20,11 +20,13 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using System.Collections.Specialized; using System.Collections.Specialized;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Comments.Buttons; using osu.Game.Overlays.Comments.Buttons;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.OSD;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Comments namespace osu.Game.Overlays.Comments
@ -71,6 +73,12 @@ namespace osu.Game.Overlays.Comments
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; private IAPIProvider api { get; set; } = null!;
[Resolved]
private GameHost host { get; set; } = null!;
[Resolved(canBeNull: true)]
private OnScreenDisplay? onScreenDisplay { get; set; }
public DrawableComment(Comment comment) public DrawableComment(Comment comment)
{ {
Comment = comment; Comment = comment;
@ -203,7 +211,6 @@ namespace osu.Game.Overlays.Comments
{ {
Name = @"Actions buttons", Name = @"Actions buttons",
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(10, 0)
}, },
actionsLoading = new LoadingSpinner actionsLoading = new LoadingSpinner
{ {
@ -323,6 +330,9 @@ namespace osu.Game.Overlays.Comments
if (WasDeleted) if (WasDeleted)
makeDeleted(); makeDeleted();
actionsContainer.AddLink("Copy link", copyUrl);
actionsContainer.AddArbitraryDrawable(new Container { Width = 10 });
if (Comment.UserId.HasValue && Comment.UserId.Value == api.LocalUser.Value.Id) if (Comment.UserId.HasValue && Comment.UserId.Value == api.LocalUser.Value.Id)
{ {
actionsContainer.AddLink("Delete", deleteComment); actionsContainer.AddLink("Delete", deleteComment);
@ -403,6 +413,12 @@ namespace osu.Game.Overlays.Comments
api.Queue(request); api.Queue(request);
} }
private void copyUrl()
{
host.GetClipboard()?.SetText($@"{api.APIEndpointUrl}/comments/{Comment.Id}");
onScreenDisplay?.Display(new CopyUrlToast());
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
ShowDeleted.BindValueChanged(show => 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(); 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() private void clear()
{ {
if (bindTarget == null) if (bindTarget == null)

View File

@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
#nullable disable using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.EnumExtensions;
@ -23,25 +22,38 @@ namespace osu.Game.Overlays
{ {
public class SettingsToolboxGroup : Container, IExpandable public class SettingsToolboxGroup : Container, IExpandable
{ {
private readonly string title;
public const int CONTAINER_WIDTH = 270; public const int CONTAINER_WIDTH = 270;
private const float transition_duration = 250; private const float transition_duration = 250;
private const int border_thickness = 2;
private const int header_height = 30; private const int header_height = 30;
private const int corner_radius = 5; private const int corner_radius = 5;
private const float fade_duration = 800;
private const float inactive_alpha = 0.5f;
private readonly Cached headerTextVisibilityCache = new Cached(); private readonly Cached headerTextVisibilityCache = new Cached();
private readonly FillFlowContainer content; protected override Container<Drawable> Content => content;
private readonly FillFlowContainer content = new FillFlowContainer
{
Name = @"Content",
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 10, Top = 5, Bottom = 10 },
Spacing = new Vector2(0, 15),
};
public BindableBool Expanded { get; } = new BindableBool(true); public BindableBool Expanded { get; } = new BindableBool(true);
private readonly OsuSpriteText headerText; private OsuSpriteText headerText = null!;
private readonly Container headerContent; private Container headerContent = null!;
private Box background = null!;
private IconButton expandButton = null!;
/// <summary> /// <summary>
/// Create a new instance. /// Create a new instance.
@ -49,20 +61,25 @@ namespace osu.Game.Overlays
/// <param name="title">The title to be displayed in the header of this group.</param> /// <param name="title">The title to be displayed in the header of this group.</param>
public SettingsToolboxGroup(string title) public SettingsToolboxGroup(string title)
{ {
this.title = title;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Width = CONTAINER_WIDTH; Width = CONTAINER_WIDTH;
Masking = true; Masking = true;
}
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider? colourProvider)
{
CornerRadius = corner_radius; CornerRadius = corner_radius;
BorderColour = Color4.Black;
BorderThickness = border_thickness;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Alpha = 0.1f,
Alpha = 0.5f, Colour = colourProvider?.Background4 ?? Color4.Black,
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -88,7 +105,7 @@ namespace osu.Game.Overlays
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17),
Padding = new MarginPadding { Left = 10, Right = 30 }, Padding = new MarginPadding { Left = 10, Right = 30 },
}, },
new IconButton expandButton = new IconButton
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
@ -99,19 +116,7 @@ namespace osu.Game.Overlays
}, },
} }
}, },
content = new FillFlowContainer content
{
Name = @"Content",
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeDuration = transition_duration,
AutoSizeEasing = Easing.OutQuint,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15),
Spacing = new Vector2(0, 15),
}
} }
}, },
}; };
@ -175,9 +180,10 @@ namespace osu.Game.Overlays
private void updateFadeState() private void updateFadeState()
{ {
this.FadeTo(IsHovered ? 1 : inactive_alpha, fade_duration, Easing.OutQuint); const float fade_duration = 500;
}
protected override Container<Drawable> Content => content; background.FadeTo(IsHovered ? 1 : 0.1f, fade_duration, Easing.OutQuint);
expandButton.FadeTo(IsHovered ? 1 : 0, fade_duration, Easing.OutQuint);
}
} }
} }

View File

@ -8,6 +8,8 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
@ -52,20 +54,32 @@ namespace osu.Game.Rulesets.Edit
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
AddInternal(RightSideToolboxContainer = new ExpandingToolboxContainer(130, 250) AddInternal(new Container
{ {
Padding = new MarginPadding(10),
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Child = new EditorToolboxGroup("snapping") RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{ {
Child = distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>> new Box
{ {
Current = { BindTarget = DistanceSpacingMultiplier }, Colour = colourProvider.Background5,
KeyboardStep = adjust_step, RelativeSizeAxes = Axes.Both,
},
RightSideToolboxContainer = new ExpandingToolboxContainer(130, 250)
{
Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1,
Child = new EditorToolboxGroup("snapping")
{
Child = distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>>
{
Current = { BindTarget = DistanceSpacingMultiplier },
KeyboardStep = adjust_step,
}
}
} }
} }
}); });
@ -91,7 +105,7 @@ namespace osu.Game.Rulesets.Edit
} }
} }
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {
switch (e.Action) switch (e.Action)
{ {
@ -103,7 +117,7 @@ namespace osu.Game.Rulesets.Edit
return false; return false;
} }
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{ {
} }

View File

@ -19,7 +19,8 @@ namespace osu.Game.Rulesets.Edit
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
FillFlow.Spacing = new Vector2(10); FillFlow.Spacing = new Vector2(5);
Padding = new MarginPadding { Vertical = 5 };
} }
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos); protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos);

View File

@ -12,10 +12,12 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Extensions.EnumExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -80,7 +82,7 @@ namespace osu.Game.Rulesets.Edit
dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
Config = Dependencies.Get<IRulesetConfigCache>().GetConfigFor(Ruleset); Config = Dependencies.Get<IRulesetConfigCache>().GetConfigFor(Ruleset);
@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Edit
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Container PlayfieldContentContainer = new Container
{ {
Name = "Content", Name = "Content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -116,25 +118,37 @@ namespace osu.Game.Rulesets.Edit
.WithChild(BlueprintContainer = CreateBlueprintContainer()) .WithChild(BlueprintContainer = CreateBlueprintContainer())
} }
}, },
new ExpandingToolboxContainer(90, 200) new Container
{ {
Padding = new MarginPadding(10), RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[] Children = new Drawable[]
{ {
new EditorToolboxGroup("toolbox (1-9)") new Box
{ {
Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X } Colour = colourProvider.Background5,
RelativeSizeAxes = Axes.Both,
}, },
new EditorToolboxGroup("toggles (Q~P)") new ExpandingToolboxContainer(60, 200)
{ {
Child = togglesCollection = new FillFlowContainer Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.X, new EditorToolboxGroup("toolbox (1-9)")
AutoSizeAxes = Axes.Y, {
Direction = FillDirection.Vertical, Child = toolboxCollection = new EditorRadioButtonCollection { RelativeSizeAxes = Axes.X }
Spacing = new Vector2(0, 5), },
}, new EditorToolboxGroup("toggles (Q~P)")
} {
Child = togglesCollection = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
},
}
}
},
} }
}, },
}; };
@ -152,6 +166,15 @@ namespace osu.Game.Rulesets.Edit
EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged;
} }
/// <summary>
/// Houses all content relevant to the playfield.
/// </summary>
/// <remarks>
/// Generally implementations should not be adding to this directly.
/// Use <see cref="LayerBelowRuleset"/> or <see cref="BlueprintContainer"/> instead.
/// </remarks>
protected Container PlayfieldContentContainer { get; private set; }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -384,7 +384,7 @@ namespace osu.Game.Rulesets.UI
// only show the cursor when within the playfield, by default. // only show the cursor when within the playfield, by default.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos); 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; public override GameplayCursorContainer Cursor => Playfield.Cursor;
@ -499,6 +499,7 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// The cursor being displayed by the <see cref="Playfield"/>. May be null if no cursor is provided. /// The cursor being displayed by the <see cref="Playfield"/>. May be null if no cursor is provided.
/// </summary> /// </summary>
[CanBeNull]
public abstract GameplayCursorContainer Cursor { get; } public abstract GameplayCursorContainer Cursor { get; }
/// <summary> /// <summary>

View File

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

View File

@ -8,13 +8,12 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -30,9 +29,9 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
public readonly RadioButton Button; public readonly RadioButton Button;
private Color4 defaultBackgroundColour; private Color4 defaultBackgroundColour;
private Color4 defaultBubbleColour; private Color4 defaultIconColour;
private Color4 selectedBackgroundColour; private Color4 selectedBackgroundColour;
private Color4 selectedBubbleColour; private Color4 selectedIconColour;
private Drawable icon; private Drawable icon;
@ -50,20 +49,13 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
defaultBackgroundColour = colours.Gray3; defaultBackgroundColour = colourProvider.Background3;
defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); selectedBackgroundColour = colourProvider.Background1;
selectedBackgroundColour = colours.BlueDark;
selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f);
Content.EdgeEffect = new EdgeEffectParameters defaultIconColour = defaultBackgroundColour.Darken(0.5f);
{ selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
Type = EdgeEffectType.Shadow,
Radius = 2,
Offset = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f)
};
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
{ {
@ -98,7 +90,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
return; return;
BackgroundColour = Button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; BackgroundColour = Button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour;
icon.Colour = Button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; icon.Colour = Button.Selected.Value ? selectedIconColour : defaultIconColour;
} }
protected override SpriteText CreateText() => new OsuSpriteText protected override SpriteText CreateText() => new OsuSpriteText

View File

@ -6,12 +6,11 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -20,9 +19,9 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
internal class DrawableTernaryButton : OsuButton internal class DrawableTernaryButton : OsuButton
{ {
private Color4 defaultBackgroundColour; private Color4 defaultBackgroundColour;
private Color4 defaultBubbleColour; private Color4 defaultIconColour;
private Color4 selectedBackgroundColour; private Color4 selectedBackgroundColour;
private Color4 selectedBubbleColour; private Color4 selectedIconColour;
private Drawable icon; private Drawable icon;
@ -38,20 +37,13 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
defaultBackgroundColour = colours.Gray3; defaultBackgroundColour = colourProvider.Background3;
defaultBubbleColour = defaultBackgroundColour.Darken(0.5f); selectedBackgroundColour = colourProvider.Background1;
selectedBackgroundColour = colours.BlueDark;
selectedBubbleColour = selectedBackgroundColour.Lighten(0.5f);
Content.EdgeEffect = new EdgeEffectParameters defaultIconColour = defaultBackgroundColour.Darken(0.5f);
{ selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
Type = EdgeEffectType.Shadow,
Radius = 2,
Offset = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f)
};
Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b => Add(icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
{ {
@ -85,17 +77,17 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
switch (Button.Bindable.Value) switch (Button.Bindable.Value)
{ {
case TernaryState.Indeterminate: case TernaryState.Indeterminate:
icon.Colour = selectedBubbleColour.Darken(0.5f); icon.Colour = selectedIconColour.Darken(0.5f);
BackgroundColour = selectedBackgroundColour.Darken(0.5f); BackgroundColour = selectedBackgroundColour.Darken(0.5f);
break; break;
case TernaryState.False: case TernaryState.False:
icon.Colour = defaultBubbleColour; icon.Colour = defaultIconColour;
BackgroundColour = defaultBackgroundColour; BackgroundColour = defaultBackgroundColour;
break; break;
case TernaryState.True: case TernaryState.True:
icon.Colour = selectedBubbleColour; icon.Colour = selectedIconColour;
BackgroundColour = selectedBackgroundColour; BackgroundColour = selectedBackgroundColour;
break; break;
} }

View File

@ -106,7 +106,6 @@ namespace osu.Game.Screens.Edit
Name = "Main content", Name = "Main content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Depth = float.MaxValue, Depth = float.MaxValue,
Padding = new MarginPadding(10),
Child = spinner = new LoadingSpinner(true) Child = spinner = new LoadingSpinner(true)
{ {
State = { Value = Visibility.Visible }, State = { Value = Visibility.Visible },

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; 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); public Bindable<LabelStyles> LabelStyle { get; } = new Bindable<LabelStyles>(LabelStyles.Icons);
private SpriteIcon arrow; private SpriteIcon arrow;
private Drawable labelEarly; private UprightAspectMaintainingContainer labelEarly;
private Drawable labelLate; private UprightAspectMaintainingContainer labelLate;
private Container colourBarsEarly; private Container colourBarsEarly;
private Container colourBarsLate; private Container colourBarsLate;
@ -122,6 +123,20 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = judgement_line_width, 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 arrowContainer = new Container
@ -261,57 +276,39 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
{ {
const float icon_size = 14; const float icon_size = 14;
labelEarly?.Expire();
labelEarly = null;
labelLate?.Expire();
labelLate = null;
switch (style) switch (style)
{ {
case LabelStyles.None: case LabelStyles.None:
break; break;
case LabelStyles.Icons: case LabelStyles.Icons:
labelEarly = new SpriteIcon labelEarly.Child = new SpriteIcon
{ {
Y = -10,
Size = new Vector2(icon_size), Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.ShippingFast, Icon = FontAwesome.Solid.ShippingFast,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
}; };
labelLate = new SpriteIcon labelLate.Child = new SpriteIcon
{ {
Y = 10,
Size = new Vector2(icon_size), Size = new Vector2(icon_size),
Icon = FontAwesome.Solid.Bicycle, Icon = FontAwesome.Solid.Bicycle,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
}; };
break; break;
case LabelStyles.Text: case LabelStyles.Text:
labelEarly = new OsuSpriteText labelEarly.Child = new OsuSpriteText
{ {
Y = -10,
Text = "Early", Text = "Early",
Font = OsuFont.Default.With(size: 10), Font = OsuFont.Default.With(size: 10),
Height = 12, Height = 12,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
}; };
labelLate = new OsuSpriteText labelLate.Child = new OsuSpriteText
{ {
Y = 10,
Text = "Late", Text = "Late",
Font = OsuFont.Default.With(size: 10), Font = OsuFont.Default.With(size: 10),
Height = 12, Height = 12,
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre,
}; };
break; break;
@ -320,26 +317,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
throw new ArgumentOutOfRangeException(nameof(style), style, null); throw new ArgumentOutOfRangeException(nameof(style), style, null);
} }
if (labelEarly != null) labelEarly.FadeInFromZero(500);
{ labelLate.FadeInFromZero(500);
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;
} }
private void createColourBars((HitResult result, double length)[] windows) private void createColourBars((HitResult result, double length)[] windows)

View File

@ -64,6 +64,8 @@ namespace osu.Game.Screens.Play
protected Task? DisposalTask { get; private set; } protected Task? DisposalTask { get; private set; }
private OsuScrollContainer settingsScroll = null!;
private bool backgroundBrightnessReduction; private bool backgroundBrightnessReduction;
private readonly BindableDouble volumeAdjustment = new BindableDouble(1); private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
@ -71,6 +73,9 @@ namespace osu.Game.Screens.Play
private AudioFilter lowPassFilter = null!; private AudioFilter lowPassFilter = null!;
private AudioFilter highPassFilter = null!; private AudioFilter highPassFilter = null!;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
protected bool BackgroundBrightnessReduction protected bool BackgroundBrightnessReduction
{ {
set set
@ -165,30 +170,30 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
new OsuScrollContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2,
Padding = new MarginPadding { Vertical = padding },
Masking = false,
Child = PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Padding = new MarginPadding { Horizontal = padding },
Children = new PlayerSettingsGroup[]
{
VisualSettings = new VisualSettings(),
AudioSettings = new AudioSettings(),
new InputSettings()
}
},
},
idleTracker = new IdleTracker(750),
}), }),
settingsScroll = new OsuScrollContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2,
Padding = new MarginPadding { Vertical = padding },
Masking = false,
Child = PlayerSettings = new FillFlowContainer<PlayerSettingsGroup>
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Padding = new MarginPadding { Horizontal = padding },
Children = new PlayerSettingsGroup[]
{
VisualSettings = new VisualSettings(),
AudioSettings = new AudioSettings(),
new InputSettings()
}
},
},
idleTracker = new IdleTracker(750),
lowPassFilter = new AudioFilter(audio.TrackMixer), lowPassFilter = new AudioFilter(audio.TrackMixer),
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass) highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
}; };
@ -224,6 +229,9 @@ namespace osu.Game.Screens.Play
Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
// Start off-screen.
settingsScroll.MoveToX(settingsScroll.DrawWidth);
content.ScaleTo(0.7f); content.ScaleTo(0.7f);
contentIn(); contentIn();
@ -313,6 +321,16 @@ namespace osu.Game.Screens.Play
content.StopTracking(); content.StopTracking();
} }
protected override void LogoSuspending(OsuLogo logo)
{
base.LogoSuspending(logo);
content.StopTracking();
logo
.FadeOut(CONTENT_OUT_DURATION / 2, Easing.OutQuint)
.ScaleTo(logo.Scale * 0.8f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
}
#endregion #endregion
protected override void Update() protected override void Update()
@ -391,6 +409,10 @@ namespace osu.Game.Screens.Play
content.FadeInFromZero(400); content.FadeInFromZero(400);
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
settingsScroll.FadeInFromZero(500, Easing.Out)
.MoveToX(0, 500, Easing.OutQuint);
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
@ -404,6 +426,10 @@ namespace osu.Game.Screens.Play
content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint); content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint); content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
settingsScroll.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint)
.MoveToX(settingsScroll.DrawWidth, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION);
highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION); highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION);
} }
@ -432,7 +458,7 @@ namespace osu.Game.Screens.Play
ContentOut(); ContentOut();
TransformSequence<PlayerLoader> pushSequence = this.Delay(CONTENT_OUT_DURATION); TransformSequence<PlayerLoader> pushSequence = this.Delay(0);
// only show if the warning was created (i.e. the beatmap needs it) // only show if the warning was created (i.e. the beatmap needs it)
// and this is not a restart of the map (the warning expires after first load). // and this is not a restart of the map (the warning expires after first load).
@ -441,6 +467,7 @@ namespace osu.Game.Screens.Play
const double epilepsy_display_length = 3000; const double epilepsy_display_length = 3000;
pushSequence pushSequence
.Delay(CONTENT_OUT_DURATION)
.Schedule(() => epilepsyWarning.State.Value = Visibility.Visible) .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
.TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint) .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
.Delay(epilepsy_display_length) .Delay(epilepsy_display_length)

View File

@ -86,16 +86,13 @@ namespace osu.Game.Screens.Play
// Generally a timeout would not happen here as APIAccess will timeout first. // Generally a timeout would not happen here as APIAccess will timeout first.
if (!tcs.Task.Wait(60000)) if (!tcs.Task.Wait(60000))
handleTokenFailure(new InvalidOperationException("Token retrieval timed out (request never run)")); req.TriggerFailure(new InvalidOperationException("Token retrieval timed out (request never run)"));
return true; return true;
void handleTokenFailure(Exception exception) void handleTokenFailure(Exception exception)
{ {
// This method may be invoked multiple times due to the Task.Wait call above. tcs.SetResult(false);
// We only really care about the first error.
if (!tcs.TrySetResult(false))
return;
if (HandleTokenRetrievalFailure(exception)) if (HandleTokenRetrievalFailure(exception))
{ {

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility
public readonly Bindable<LatencyVisualMode> VisualMode = new Bindable<LatencyVisualMode>(); 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; public bool ProvidingUserCursor => IsActiveArea.Value;
@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
MenuCursor = new LatencyCursorContainer Cursor = new LatencyCursorContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
MenuCursor = new LatencyCursorContainer Cursor = new LatencyCursorContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
MenuCursor = new LatencyCursorContainer Cursor = new LatencyCursorContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },

View File

@ -17,6 +17,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -42,6 +43,8 @@ namespace osu.Game.Tests.Visual
protected override bool CreateNestedActionContainer => false; protected override bool CreateNestedActionContainer => false;
protected override bool DisplayCursorForManualInput => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -119,6 +122,8 @@ namespace osu.Game.Tests.Visual
public RealmAccess Realm => Dependencies.Get<RealmAccess>(); public RealmAccess Realm => Dependencies.Get<RealmAccess>();
public new GlobalCursorDisplay GlobalCursorDisplay => base.GlobalCursorDisplay;
public new BackButton BackButton => base.BackButton; public new BackButton BackButton => base.BackButton;
public new BeatmapManager BeatmapManager => base.BeatmapManager; public new BeatmapManager BeatmapManager => base.BeatmapManager;

View File

@ -36,21 +36,31 @@ namespace osu.Game.Tests.Visual
/// </summary> /// </summary>
protected virtual bool CreateNestedActionContainer => true; 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() protected OsuManualInputManagerTestScene()
{ {
GlobalCursorDisplay cursorDisplay; var mainContent = content = new Container { RelativeSizeAxes = Axes.Both };
CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; if (DisplayCursorForManualInput)
cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor)
{ {
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) if (CreateNestedActionContainer)
{
mainContent = new GlobalActionContainer(null).WithChild(mainContent); mainContent = new GlobalActionContainer(null).WithChild(mainContent);
}
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {