diff --git a/osu.Android.props b/osu.Android.props
index 454bb46059..ec223f98c2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index a3307c9224..6abfbdbe21 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
AddSliderStep("circle size", 0, 8, 5, createCatcher);
AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t)));
+ AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
AddStep("catch many random fruit", () =>
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index e736d68740..371e901c69 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
Banana,
Droplet,
Catcher,
- CatchComboCounter
+ CatchComboCounter,
+ HitExplosion
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
new file mode 100644
index 0000000000..e1fad564a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
@@ -0,0 +1,129 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Utils;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ private CircularContainer largeFaint;
+ private CircularContainer smallFaint;
+ private CircularContainer directionalGlow1;
+ private CircularContainer directionalGlow2;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+
+ // scale roughly in-line with visual appearance of notes
+ const float initial_height = 10;
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ smallFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow2 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ }
+ };
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ X = entry.Position;
+ Scale = new Vector2(entry.HitObject.Scale);
+ setColour(entry.ObjectColour);
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ applyTransforms(entry.HitObject.RandomSeed);
+ }
+
+ private void applyTransforms(int randomSeed)
+ {
+ const double duration = 400;
+
+ // we want our size to be very small so the glow dominates it.
+ largeFaint.Size = new Vector2(0.8f);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ const float angle_variangle = 15; // should be less than 45
+ directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
+ directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ }
+
+ private void setColour(Color4 objectColour)
+ {
+ const float roundness = 100;
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+
+ smallFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+
+ directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 5e744ec001..10fc4e78b2 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
if (version < 2.3m)
{
- if (GetTexture(@"fruit-ryuuta") != null ||
- GetTexture(@"fruit-ryuuta-0") != null)
+ if (hasOldStyleCatcherSprite())
return new LegacyCatcherOld();
}
- if (GetTexture(@"fruit-catcher-idle") != null ||
- GetTexture(@"fruit-catcher-idle-0") != null)
+ if (hasNewStyleCatcherSprite())
return new LegacyCatcherNew();
return null;
@@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
return new LegacyCatchComboCounter(Skin);
return null;
+
+ case CatchSkinComponents.HitExplosion:
+ if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
+ return new LegacyHitExplosion();
+
+ return null;
}
}
return base.GetDrawableComponent(component);
}
+ private bool hasOldStyleCatcherSprite() =>
+ GetTexture(@"fruit-ryuuta") != null
+ || GetTexture(@"fruit-ryuuta-0") != null;
+
+ private bool hasNewStyleCatcherSprite() =>
+ GetTexture(@"fruit-catcher-idle") != null
+ || GetTexture(@"fruit-catcher-idle-0") != null;
+
public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
new file mode 100644
index 0000000000..c262b0a4ac
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
+{
+ public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ [Resolved]
+ private Catcher catcher { get; set; }
+
+ private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
+
+ private readonly Sprite explosion1;
+ private readonly Sprite explosion2;
+
+ public LegacyHitExplosion()
+ {
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ RelativeSizeAxes = Axes.Both;
+ Scale = new Vector2(0.5f);
+
+ InternalChildren = new[]
+ {
+ explosion1 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ },
+ explosion2 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(SkinManager skins)
+ {
+ var defaultLegacySkin = skins.DefaultLegacySkin;
+
+ // sprite names intentionally swapped to match stable member naming / ease of cross-referencing
+ explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
+ explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ Colour = entry.ObjectColour;
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ {
+ float halfCatchWidth = catcher.CatchWidth / 2;
+ float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
+
+ if (!(entry.HitObject is Droplet))
+ {
+ float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
+
+ explosion1.Scale = new Vector2(1, 0.9f);
+ explosion1.Position = new Vector2(explosionOffset, 0);
+
+ explosion1.FadeOutFromOne(300);
+ explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
+ }
+
+ explosion2.Scale = new Vector2(0.9f, 1);
+ explosion2.Position = new Vector2(explosionOffset, 0);
+
+ explosion2.FadeOutFromOne(700);
+ explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
+
+ this.Delay(700).FadeOutFromOne();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 9fd4610e6e..5cd85aac56 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -23,6 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
+ [Cached]
public class Catcher : SkinReloadableDrawable
{
///
@@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Width of the area that can be used to attempt catches during gameplay.
///
- private readonly float catchWidth;
+ public readonly float CatchWidth;
private readonly SkinnableCatcher body;
@@ -133,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (difficulty != null)
Scale = calculateScale(difficulty);
- catchWidth = CalculateCatchWidth(Scale);
+ CatchWidth = CalculateCatchWidth(Scale);
InternalChildren = new Drawable[]
{
@@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (!(hitObject is PalpableCatchHitObject fruit))
return false;
- float halfCatchWidth = catchWidth * 0.5f;
+ float halfCatchWidth = CatchWidth * 0.5f;
return fruit.EffectiveX >= X - halfCatchWidth &&
fruit.EffectiveX <= X + halfCatchWidth;
}
@@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
placeCaughtObject(palpableObject, positionInStack);
if (hitLighting.Value)
- addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
+ addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
}
// droplet doesn't affect the catcher state
@@ -365,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
return position;
}
- private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
- hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
+ private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
+ hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
{
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
index d9ab428231..955b1e6edb 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -1,129 +1,56 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.Objects.Pooling;
-using osu.Game.Utils;
-using osuTK;
-using osuTK.Graphics;
+using osu.Game.Skinning;
+
+#nullable enable
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosion : PoolableDrawableWithLifetime
{
- private readonly CircularContainer largeFaint;
- private readonly CircularContainer smallFaint;
- private readonly CircularContainer directionalGlow1;
- private readonly CircularContainer directionalGlow2;
+ private readonly SkinnableDrawable skinnableExplosion;
public HitExplosion()
{
- Size = new Vector2(20);
- Anchor = Anchor.TopCentre;
+ RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
- // scale roughly in-line with visual appearance of notes
- const float initial_height = 10;
-
- InternalChildren = new Drawable[]
+ InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
{
- largeFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- smallFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- directionalGlow1 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- },
- directionalGlow2 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- }
+ CentreComponent = false,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre
};
}
protected override void OnApply(HitExplosionEntry entry)
{
- X = entry.Position;
- Scale = new Vector2(entry.Scale);
- setColour(entry.ObjectColour);
-
- using (BeginAbsoluteSequence(entry.LifetimeStart))
- applyTransforms(entry.RNGSeed);
+ base.OnApply(entry);
+ if (IsLoaded)
+ apply(entry);
}
- private void applyTransforms(int randomSeed)
+ protected override void LoadComplete()
{
+ base.LoadComplete();
+ apply(Entry);
+ }
+
+ private void apply(HitExplosionEntry? entry)
+ {
+ if (entry == null)
+ return;
+
+ ApplyTransformsAt(double.MinValue, true);
ClearTransforms(true);
- const double duration = 400;
-
- // we want our size to be very small so the glow dominates it.
- largeFaint.Size = new Vector2(0.8f);
- largeFaint
- .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
- .FadeOut(duration * 2);
-
- const float angle_variangle = 15; // should be less than 45
- directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
- directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
-
- this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
- }
-
- private void setColour(Color4 objectColour)
- {
- const float roundness = 100;
-
- largeFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- };
-
- smallFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- };
-
- directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
- Roundness = roundness,
- Radius = 40,
- };
+ (skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
+ LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
index 094d88243a..6df13e52ef 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects.Pooling;
@@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
public HitExplosionContainer()
{
+ RelativeSizeAxes = Axes.Both;
+
AddInternal(pool = new DrawablePool(10));
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
index b142962a8a..88871c77f6 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
@@ -2,24 +2,42 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Performance;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
using osuTK.Graphics;
+#nullable enable
+
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosionEntry : LifetimeEntry
{
- public readonly float Position;
- public readonly float Scale;
- public readonly Color4 ObjectColour;
- public readonly int RNGSeed;
+ ///
+ /// The judgement result that triggered this explosion.
+ ///
+ public JudgementResult JudgementResult { get; }
- public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
+ ///
+ /// The hitobject which triggered this explosion.
+ ///
+ public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
+
+ ///
+ /// The accent colour of the object caught.
+ ///
+ public Color4 ObjectColour { get; }
+
+ ///
+ /// The position at which the object was caught.
+ ///
+ public float Position { get; }
+
+ public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
{
LifetimeStart = startTime;
Position = position;
- Scale = scale;
+ JudgementResult = judgementResult;
ObjectColour = objectColour;
- RNGSeed = rngSeed;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs b/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
new file mode 100644
index 0000000000..c744c00d9a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ ///
+ /// Common interface for all hit explosion skinnables.
+ ///
+ public interface IHitExplosion
+ {
+ ///
+ /// Begins animating this .
+ ///
+ void Animate(HitExplosionEntry entry);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 636cd63c69..3102db270e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
@@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
+ drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield, drawableRuleset.Beatmap));
}
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
@@ -128,8 +129,21 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override void Update()
{
- float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
- float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
+ float start, end;
+
+ if (Precision.AlmostEquals(restrictTo.Rotation, 0))
+ {
+ start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
+ end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
+ }
+ else
+ {
+ float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
+ float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
+
+ start = center - halfDiagonal;
+ end = center + halfDiagonal;
+ }
float rawWidth = end - start;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
index f6fd3e36ab..587ff4b573 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
@@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
@@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable cursorSize;
+ private Vector2? currentPosition;
+
public LegacyCursorTrail(ISkin skin)
{
this.skin = skin;
@@ -54,22 +57,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
protected override double FadeDuration => disjointTrail ? 150 : 500;
+ protected override float FadeExponent => 1;
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!disjointTrail || !currentPosition.HasValue)
+ return;
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ AddTrail(currentPosition.Value);
+ }
+ }
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!disjointTrail)
return base.OnMouseMove(e);
- if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
- {
- lastTrailTime = Time.Current;
- return base.OnMouseMove(e);
- }
+ currentPosition = e.ScreenSpaceMousePosition;
+ // Intentionally block the base call as we're adding the trails ourselves.
return false;
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 7f86e9daf7..7a95111c91 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
private const int max_sprites = 2048;
+ ///
+ /// An exponentiating factor to ease the trail fade.
+ ///
+ protected virtual float FadeExponent => 1.7f;
+
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private int currentIndex;
private IShader shader;
@@ -141,22 +146,25 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override bool OnMouseMove(MouseMoveEvent e)
{
- Vector2 pos = e.ScreenSpaceMousePosition;
+ AddTrail(e.ScreenSpaceMousePosition);
+ return base.OnMouseMove(e);
+ }
- if (lastPosition == null)
+ protected void AddTrail(Vector2 position)
+ {
+ if (InterpolateMovements)
{
- lastPosition = pos;
- resampler.AddPosition(lastPosition.Value);
- return base.OnMouseMove(e);
- }
-
- foreach (Vector2 pos2 in resampler.AddPosition(pos))
- {
- Trace.Assert(lastPosition.HasValue);
-
- if (InterpolateMovements)
+ if (!lastPosition.HasValue)
{
- // ReSharper disable once PossibleInvalidOperationException
+ lastPosition = position;
+ resampler.AddPosition(lastPosition.Value);
+ return;
+ }
+
+ foreach (Vector2 pos2 in resampler.AddPosition(position))
+ {
+ Trace.Assert(lastPosition.HasValue);
+
Vector2 pos1 = lastPosition.Value;
Vector2 diff = pos2 - pos1;
float distance = diff.Length;
@@ -170,14 +178,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
addPart(lastPosition.Value);
}
}
- else
- {
- lastPosition = pos2;
- addPart(lastPosition.Value);
- }
}
-
- return base.OnMouseMove(e);
+ else
+ {
+ lastPosition = position;
+ addPart(lastPosition.Value);
+ }
}
private void addPart(Vector2 screenSpacePosition)
@@ -206,10 +212,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Texture texture;
private float time;
+ private float fadeExponent;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
-
private Vector2 originPosition;
private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
@@ -227,6 +233,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
texture = Source.texture;
size = Source.partSize;
time = Source.time;
+ fadeExponent = Source.FadeExponent;
originPosition = Vector2.Zero;
@@ -249,6 +256,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
+ shader.GetUniform("g_FadeExponent").UpdateValue(ref fadeExponent);
texture.TextureGL.Bind();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index 059432eeaf..855a75117d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
- protected override ISkin GetSkin() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
index 0ec21a4c7b..5e22101e5c 100644
--- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs
+++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -19,6 +20,7 @@ namespace osu.Game.Tests.Chat
{
private ChannelManager channelManager;
private int currentMessageId;
+ private List sentMessages;
[SetUp]
public void Setup() => Schedule(() =>
@@ -34,6 +36,7 @@ namespace osu.Game.Tests.Chat
AddStep("register request handling", () =>
{
currentMessageId = 0;
+ sentMessages = new List();
((DummyAPIAccess)API).HandleRequest = req =>
{
@@ -44,16 +47,11 @@ namespace osu.Game.Tests.Chat
return true;
case PostMessageRequest postMessage:
- postMessage.TriggerSuccess(new Message(++currentMessageId)
- {
- IsAction = postMessage.Message.IsAction,
- ChannelId = postMessage.Message.ChannelId,
- Content = postMessage.Message.Content,
- Links = postMessage.Message.Links,
- Timestamp = postMessage.Message.Timestamp,
- Sender = postMessage.Message.Sender
- });
+ handlePostMessageRequest(postMessage);
+ return true;
+ case MarkChannelAsReadRequest markRead:
+ handleMarkChannelAsReadRequest(markRead);
return true;
}
@@ -83,12 +81,65 @@ namespace osu.Game.Tests.Chat
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
}
+ [Test]
+ public void TestMarkAsReadIgnoringLocalMessages()
+ {
+ Channel channel = null;
+
+ AddStep("join channel and select it", () =>
+ {
+ channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
+ channelManager.CurrentChannel.Value = channel;
+ });
+
+ AddStep("post message", () => channelManager.PostMessage("Something interesting"));
+
+ AddStep("post /help command", () => channelManager.PostCommand("help", channel));
+ AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
+ AddStep("post /join command with no channel", () => channelManager.PostCommand("join", channel));
+ AddStep("post /join command with non-existent channel", () => channelManager.PostCommand("join i-dont-exist", channel));
+ AddStep("post non-existent command", () => channelManager.PostCommand("non-existent-cmd arg", channel));
+
+ AddStep("mark channel as read", () => channelManager.MarkChannelAsRead(channel));
+ AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
+ }
+
+ private void handlePostMessageRequest(PostMessageRequest request)
+ {
+ var message = new Message(++currentMessageId)
+ {
+ IsAction = request.Message.IsAction,
+ ChannelId = request.Message.ChannelId,
+ Content = request.Message.Content,
+ Links = request.Message.Links,
+ Timestamp = request.Message.Timestamp,
+ Sender = request.Message.Sender
+ };
+
+ sentMessages.Add(message);
+ request.TriggerSuccess(message);
+ }
+
+ private void handleMarkChannelAsReadRequest(MarkChannelAsReadRequest request)
+ {
+ // only accept messages that were sent through the API
+ if (sentMessages.Contains(request.Message))
+ {
+ request.TriggerSuccess();
+ }
+ else
+ {
+ request.TriggerFailure(new APIException("unknown message!", null));
+ }
+ }
+
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
{
Id = id,
Name = $"Channel {id}",
Topic = $"Topic of channel {id} with type {type}",
Type = type,
+ LastMessageId = 0,
};
private class ChannelManagerContainer : CompositeDrawable
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index aed28f5f84..3bf6aaac7a 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -204,7 +204,7 @@ namespace osu.Game.Tests.Gameplay
this.resources = resources;
}
- protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
+ protected internal override ISkin GetSkin() => new TestSkin("test-sample", resources);
}
private class TestDrawableStoryboardSample : DrawableStoryboardSample
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index c15d804a19..aadabec100 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -133,11 +133,12 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestEmptyComboColoursNoFallback()
{
- AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours(
+ AddStep("Add custom combo colours to user skin", () => userSource.Configuration.CustomComboColours = new List
+ {
new Color4(100, 150, 200, 255),
new Color4(55, 110, 166, 255),
new Color4(75, 125, 175, 255)
- ));
+ });
AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 13e84e335d..e560c81fb2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
this.beatmapSkin = beatmapSkin;
}
- protected override ISkin GetSkin() => beatmapSkin;
+ protected internal override ISkin GetSkin() => beatmapSkin;
}
private class TestOsuRuleset : OsuRuleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
index 17fe09f2c6..0441c5641e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
+ AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
}
@@ -83,19 +84,38 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
}
+ [Test]
+ public void TestMaxHeight()
+ {
+ int playerNumber = 1;
+ AddRepeatStep("add 3 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 3);
+ checkHeight(4);
+
+ AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
+ checkHeight(8);
+
+ AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
+ checkHeight(8);
+
+ void checkHeight(int panelCount)
+ => AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
+ }
+
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
{
- var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
+ var leaderboardScore = leaderboard.Add(user, isTracked);
leaderboardScore.TotalScore.BindTo(score);
}
private class TestGameplayLeaderboard : GameplayLeaderboard
{
+ public float Spacing => Flow.Spacing.Y;
+
public bool CheckPositionByUsername(string username, int? expectedPosition)
{
- var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
+ var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username);
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index b7e92a79a0..3017428039 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -12,6 +12,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Skinning;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@@ -142,6 +143,22 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
+ [Test]
+ public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
+ {
+ HUDVisibilityMode originalConfigValue = default;
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
+
+ AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+
+ createNew();
+ AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
+ AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
+
+ AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ }
+
private void createNew(Action action = null)
{
AddStep("create overlay", () =>
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
new file mode 100644
index 0000000000..e58f85b0b3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Overlays;
+using osu.Game.Tests.Visual.Navigation;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ public class TestSceneSideOverlays : OsuGameTestScene
+ {
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddAssert("no screen offset applied", () => Game.ScreenOffsetContainer.X == 0f);
+ AddUntilStep("wait for overlays", () => Game.Settings.IsLoaded && Game.Notifications.IsLoaded);
+ }
+
+ [Test]
+ public void TestScreenOffsettingOnSettingsOverlay()
+ {
+ AddStep("open settings", () => Game.Settings.Show());
+ AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+
+ AddStep("hide settings", () => Game.Settings.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
+
+ [Test]
+ public void TestScreenOffsettingOnNotificationOverlay()
+ {
+ AddStep("open notifications", () => Game.Notifications.Show());
+ AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+
+ AddStep("hide notifications", () => Game.Notifications.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
new file mode 100644
index 0000000000..af874cec91
--- /dev/null
+++ b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Mods
+{
+ public class TestSceneModFailCondition : ModTestScene
+ {
+ private bool restartRequested;
+
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+
+ protected override TestPlayer CreateModPlayer(Ruleset ruleset)
+ {
+ var player = base.CreateModPlayer(ruleset);
+ player.RestartRequested = () => restartRequested = true;
+ return player;
+ }
+
+ protected override bool AllowFail => true;
+
+ [SetUpSteps]
+ public void SetUp()
+ {
+ AddStep("reset flag", () => restartRequested = false);
+ }
+
+ [Test]
+ public void TestRestartOnFailDisabled() => CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Mod = new OsuModSuddenDeath(),
+ PassCondition = () => !restartRequested && Player.ChildrenOfType().Single().State.Value == Visibility.Visible
+ });
+
+ [Test]
+ public void TestRestartOnFailEnabled() => CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Mod = new OsuModSuddenDeath
+ {
+ Restart = { Value = true }
+ },
+ PassCondition = () => restartRequested && Player.ChildrenOfType().Single().State.Value == Visibility.Hidden
+ });
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 0ffa5209e3..08b3fb98a8 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -25,7 +25,7 @@ using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
-using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
@@ -87,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestEmpty()
{
// used to test the flow of multiplayer from visual tests.
+ AddStep("empty step", () => { });
}
[Test]
@@ -312,6 +313,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
@@ -348,6 +351,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddStep("restore beatmap", () =>
@@ -396,7 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
- AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).TriggerClick());
+ AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick());
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
@@ -404,8 +409,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
- testLeave("lounge tab item", () => this.ChildrenOfType.BreadcrumbTabItem>().First().TriggerClick());
-
testLeave("back button", () => multiplayerScreen.OnBackButton());
// mimics home button and OS window close
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 955be6ca21..ea10fc1b8b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value);
AddStep("click ready button", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index a3e6c8de3b..c4ebc13245 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
@@ -48,9 +49,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1);
- AddStep("add non-resolvable user", () => Client.AddNullUser(-3));
+ AddStep("add non-resolvable user", () => Client.AddNullUser());
+ AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2);
+
+ AddStep("kick null user", () => this.ChildrenOfType().Single(p => p.User.User == null)
+ .ChildrenOfType().Single().TriggerClick());
+
+ AddAssert("null user kicked", () => Client.Room.AsNonNull().Users.Count == 1);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
new file mode 100644
index 0000000000..ff06d4d9c7
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
@@ -0,0 +1,51 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerResults : ScreenTestScene
+ {
+ [Test]
+ public void TestDisplayResults()
+ {
+ MultiplayerResultsScreen screen = null;
+
+ AddStep("show results screen", () =>
+ {
+ var rulesetInfo = new OsuRuleset().RulesetInfo;
+ var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
+
+ var score = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ Beatmap = beatmapInfo,
+ User = new User { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineScoreID = 12345,
+ Ruleset = rulesetInfo,
+ };
+
+ PlaylistItem playlistItem = new PlaylistItem
+ {
+ BeatmapID = beatmapInfo.ID,
+ };
+
+ Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem));
+ });
+
+ AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
new file mode 100644
index 0000000000..0a8bda7ec0
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
@@ -0,0 +1,61 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerTeamResults : ScreenTestScene
+ {
+ [TestCase(7483253, 1048576)]
+ [TestCase(1048576, 7483253)]
+ [TestCase(1048576, 1048576)]
+ public void TestDisplayTeamResults(int team1Score, int team2Score)
+ {
+ MultiplayerResultsScreen screen = null;
+
+ AddStep("show results screen", () =>
+ {
+ var rulesetInfo = new OsuRuleset().RulesetInfo;
+ var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
+
+ var score = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ Beatmap = beatmapInfo,
+ User = new User { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineScoreID = 12345,
+ Ruleset = rulesetInfo,
+ };
+
+ PlaylistItem playlistItem = new PlaylistItem
+ {
+ BeatmapID = beatmapInfo.ID,
+ };
+
+ SortedDictionary teamScores = new SortedDictionary
+ {
+ { 0, new BindableInt(team1Score) },
+ { 1, new BindableInt(team2Score) }
+ };
+
+ Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores));
+ });
+
+ AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index f9a991f756..c9a1471e41 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
@@ -95,6 +96,8 @@ namespace osu.Game.Tests.Visual.Navigation
public class TestOsuGame : OsuGame
{
+ public new const float SIDE_OVERLAY_OFFSET_RATIO = OsuGame.SIDE_OVERLAY_OFFSET_RATIO;
+
public new ScreenStack ScreenStack => base.ScreenStack;
public new BackButton BackButton => base.BackButton;
@@ -103,7 +106,11 @@ namespace osu.Game.Tests.Visual.Navigation
public new ScoreManager ScoreManager => base.ScoreManager;
- public new SettingsPanel Settings => base.Settings;
+ public new Container ScreenOffsetContainer => base.ScreenOffsetContainer;
+
+ public new SettingsOverlay Settings => base.Settings;
+
+ public new NotificationOverlay Notifications => base.Notifications;
public new MusicController MusicController => base.MusicController;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
similarity index 70%
rename from osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
rename to osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
index 7fdf0708e0..628ae0971b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
@@ -3,84 +3,52 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
-using osu.Game.Graphics.UserInterface;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osuTK;
using JetBrains.Annotations;
-using NUnit.Framework;
+using osu.Framework.Testing;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneCommentsPage : OsuTestScene
+ public class TestSceneOfflineCommentsContainer : OsuTestScene
{
[Cached]
- private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- private readonly BindableBool showDeleted = new BindableBool();
- private readonly Container content;
+ private TestCommentsContainer comments;
- private TestCommentsPage commentsPage;
-
- public TestSceneCommentsPage()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Add(new FillFlowContainer
+ Clear();
+ Add(new BasicScrollContainer
{
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 10),
- Children = new Drawable[]
- {
- new Container
- {
- AutoSizeAxes = Axes.Y,
- Width = 200,
- Child = new OsuCheckbox
- {
- Current = showDeleted,
- LabelText = @"Show Deleted"
- }
- },
- content = new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- }
- }
+ RelativeSizeAxes = Axes.Both,
+ Child = comments = new TestCommentsContainer()
});
- }
+ });
[Test]
public void TestAppendDuplicatedComment()
{
- AddStep("Create page", () => createPage(getCommentBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
- AddStep("Append existing comment", () => commentsPage?.AppendComments(getCommentSubBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddUntilStep("Dictionary length is 10", () => comments.DictionaryLength == 10);
+ AddStep("Append existing comment", () => comments.AppendComments(getCommentSubBundle()));
+ AddAssert("Dictionary length is 10", () => comments.DictionaryLength == 10);
}
[Test]
- public void TestEmptyBundle()
+ public void TestLocalCommentBundle()
{
- AddStep("Create page", () => createPage(getEmptyCommentBundle()));
- AddAssert("Dictionary length is 0", () => commentsPage?.DictionaryLength == 0);
- }
-
- private void createPage(CommentBundle commentBundle)
- {
- commentsPage = null;
- content.Clear();
- content.Add(commentsPage = new TestCommentsPage(commentBundle)
- {
- ShowDeleted = { BindTarget = showDeleted }
- });
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddStep("Add empty comment bundle", () => comments.ShowComments(getEmptyCommentBundle()));
}
private CommentBundle getEmptyCommentBundle() => new CommentBundle
@@ -193,6 +161,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "Good_Admin"
}
},
+ Total = 10
};
private CommentBundle getCommentSubBundle() => new CommentBundle
@@ -211,16 +180,18 @@ namespace osu.Game.Tests.Visual.Online
IncludedComments = new List(),
};
- private class TestCommentsPage : CommentsPage
+ private class TestCommentsContainer : CommentsContainer
{
- public TestCommentsPage(CommentBundle commentBundle)
- : base(commentBundle)
- {
- }
-
public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle);
public int DictionaryLength => CommentDictionary.Count;
+
+ public void ShowComments(CommentBundle bundle)
+ {
+ this.ChildrenOfType().Single().Current.Value = 0;
+ ClearComments();
+ OnSuccess(bundle);
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs
deleted file mode 100644
index 40e191dd7e..0000000000
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Tests.Visual.Playlists
-{
- public class TestScenePlaylistsFilterControl : OsuTestScene
- {
- public TestScenePlaylistsFilterControl()
- {
- Child = new PlaylistsFilterControl
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Width = 0.7f,
- Height = 80,
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index ecdb046203..aff0e7ba4b 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -62,6 +62,24 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
}
+ [Test]
+ public void TestEnteringRoomTakesLeaseOnSelection()
+ {
+ AddStep("add rooms", () => RoomManager.AddRooms(1));
+
+ AddAssert("selected room is not disabled", () => !OnlinePlayDependencies.SelectedRoom.Disabled);
+
+ AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
+ AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
+
+ AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick());
+
+ AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen);
+
+ AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
+ AddAssert("selected room is disabled", () => OnlinePlayDependencies.SelectedRoom.Disabled);
+ }
+
private bool checkRoomVisible(DrawableRoom room) =>
loungeScreen.ChildrenOfType().First().ScreenSpaceDrawQuad
.Contains(room.ScreenSpaceDrawQuad.Centre);
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
index a62980addf..da474a64ba 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Settings
new TabletSettings(tabletHandler)
{
RelativeSizeAxes = Axes.None,
- Width = SettingsPanel.WIDTH,
+ Width = SettingsPanel.PANEL_WIDTH,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
index e0d76b3e4a..f8652573f4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
@@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -174,6 +176,60 @@ namespace osu.Game.Tests.Visual.UserInterface
checkBindableAtValue("Circle Size", null);
}
+ [Test]
+ public void TestModSettingChangeTracker()
+ {
+ ModSettingChangeTracker tracker = null;
+ Queue settingsChangedQueue = null;
+
+ setBeatmapWithDifficultyParameters(5);
+
+ AddStep("add mod settings change tracker", () =>
+ {
+ settingsChangedQueue = new Queue();
+
+ tracker = new ModSettingChangeTracker(modDifficultyAdjust.Yield())
+ {
+ SettingChanged = settingsChangedQueue.Enqueue
+ };
+ });
+
+ AddAssert("no settings changed", () => settingsChangedQueue.Count == 0);
+
+ setSliderValue("Circle Size", 3);
+
+ settingsChangedFired();
+
+ setSliderValue("Circle Size", 5);
+ checkBindableAtValue("Circle Size", 5);
+
+ settingsChangedFired();
+
+ AddStep("reset mod settings", () => modDifficultyAdjust.CircleSize.SetDefault());
+ checkBindableAtValue("Circle Size", null);
+
+ settingsChangedFired();
+
+ setExtendedLimits(true);
+
+ settingsChangedFired();
+
+ AddStep("dispose tracker", () =>
+ {
+ tracker.Dispose();
+ tracker = null;
+ });
+
+ void settingsChangedFired()
+ {
+ AddAssert("setting changed event fired", () =>
+ {
+ settingsChangedQueue.Dequeue();
+ return settingsChangedQueue.Count == 0;
+ });
+ }
+ }
+
private void resetToDefault(string name)
{
AddStep($"Reset {name} to default", () =>
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 5477e4a0f8..9c85fa0c9c 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 0d16294c68..4a78ceb299 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -534,7 +534,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetBeatmapTrack() => null;
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index d78ffbbfb6..45112ae74c 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps
return storyboard;
}
- protected override ISkin GetSkin()
+ protected internal override ISkin GetSkin()
{
try
{
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index ea7f45e53f..acfd01a3c8 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack() => GetVirtualTrack();
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
index 41c85db063..853a590595 100644
--- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using osuTK.Graphics;
@@ -13,9 +14,17 @@ namespace osu.Game.Beatmaps.Formats
///
IReadOnlyList ComboColours { get; }
+ ///
+ /// The list of custom combo colours.
+ /// If non-empty, will return these colours;
+ /// if empty, will fall back to default combo colours.
+ ///
+ List CustomComboColours { get; }
+
///
/// Adds combo colours to the list.
///
+ [Obsolete("Use CustomComboColours directly.")] // can be removed 20220215
void AddComboColours(params Color4[] colours);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index b39890084f..20080308f9 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -123,7 +123,7 @@ namespace osu.Game.Beatmaps.Formats
{
if (!(output is IHasComboColours tHasComboColours)) return;
- tHasComboColours.AddComboColours(colour);
+ tHasComboColours.CustomComboColours.Add(colour);
}
else
{
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 662d24cc83..61760e69b0 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -327,7 +327,15 @@ namespace osu.Game.Beatmaps
public bool SkinLoaded => skin.IsResultAvailable;
public ISkin Skin => skin.Value;
- protected abstract ISkin GetSkin();
+ ///
+ /// Creates a new skin instance for this beatmap.
+ ///
+ ///
+ /// This should only be called externally in scenarios where it is explicitly desired to get a new instance of a skin
+ /// (e.g. for editing purposes, to avoid state pollution).
+ /// For standard reading purposes, should always be used directly.
+ ///
+ protected internal abstract ISkin GetSkin();
private readonly RecyclableLazy skin;
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 60a0d5a0ac..9b0d7f51da 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -201,6 +201,8 @@ namespace osu.Game.Configuration
public Func LookupKeyBindings { get; set; }
}
+ // IMPORTANT: These are used in user configuration files.
+ // The naming of these keys should not be changed once they are deployed in a release, unless migration logic is also added.
public enum OsuSetting
{
Ruleset,
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
new file mode 100644
index 0000000000..aa6eabd7d1
--- /dev/null
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -0,0 +1,64 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class AudioSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.AudioSettings";
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSectionHeader => new TranslatableString(getKey(@"audio_section_header"), @"Audio");
+
+ ///
+ /// "Devices"
+ ///
+ public static LocalisableString AudioDevicesHeader => new TranslatableString(getKey(@"audio_devices_header"), @"Devices");
+
+ ///
+ /// "Volume"
+ ///
+ public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
+
+ ///
+ /// "Master"
+ ///
+ public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
+
+ ///
+ /// "Master (window inactive)"
+ ///
+ public static LocalisableString MasterVolumeInactive => new TranslatableString(getKey(@"master_volume_inactive"), @"Master (window inactive)");
+
+ ///
+ /// "Effect"
+ ///
+ public static LocalisableString EffectVolume => new TranslatableString(getKey(@"effect_volume"), @"Effect");
+
+ ///
+ /// "Music"
+ ///
+ public static LocalisableString MusicVolume => new TranslatableString(getKey(@"music_volume"), @"Music");
+
+ ///
+ /// "Offset Adjustment"
+ ///
+ public static LocalisableString OffsetHeader => new TranslatableString(getKey(@"offset_header"), @"Offset Adjustment");
+
+ ///
+ /// "Audio offset"
+ ///
+ public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset");
+
+ ///
+ /// "Offset wizard"
+ ///
+ public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
index bf488d2590..432c1c6255 100644
--- a/osu.Game/Localisation/CommonStrings.cs
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -14,11 +14,21 @@ namespace osu.Game.Localisation
///
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
+ ///
+ /// "Clear"
+ ///
+ public static LocalisableString Clear => new TranslatableString(getKey(@"clear"), @"Clear");
+
///
/// "Enabled"
///
public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled");
+ ///
+ /// "Default"
+ ///
+ public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
+
///
/// "Width"
///
@@ -31,4 +41,4 @@ namespace osu.Game.Localisation
private static string getKey(string key) => $@"{prefix}:{key}";
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs
new file mode 100644
index 0000000000..dd21739096
--- /dev/null
+++ b/osu.Game/Localisation/DebugSettingsStrings.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class DebugSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
+
+ ///
+ /// "Debug"
+ ///
+ public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Show log overlay"
+ ///
+ public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
+
+ ///
+ /// "Bypass front-to-back render pass"
+ ///
+ public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
+
+ ///
+ /// "Import files"
+ ///
+ public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files");
+
+ ///
+ /// "Memory"
+ ///
+ public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
+
+ ///
+ /// "Clear all caches"
+ ///
+ public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs
new file mode 100644
index 0000000000..6d6381b429
--- /dev/null
+++ b/osu.Game/Localisation/GameplaySettingsStrings.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GameplaySettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GameplaySettings";
+
+ ///
+ /// "Gameplay"
+ ///
+ public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Background dim"
+ ///
+ public static LocalisableString BackgroundDim => new TranslatableString(getKey(@"dim"), @"Background dim");
+
+ ///
+ /// "Background blur"
+ ///
+ public static LocalisableString BackgroundBlur => new TranslatableString(getKey(@"blur"), @"Background blur");
+
+ ///
+ /// "Lighten playfield during breaks"
+ ///
+ public static LocalisableString LightenDuringBreaks => new TranslatableString(getKey(@"lighten_during_breaks"), @"Lighten playfield during breaks");
+
+ ///
+ /// "HUD overlay visibility mode"
+ ///
+ public static LocalisableString HUDVisibilityMode => new TranslatableString(getKey(@"hud_visibility_mode"), @"HUD overlay visibility mode");
+
+ ///
+ /// "Show difficulty graph on progress bar"
+ ///
+ public static LocalisableString ShowDifficultyGraph => new TranslatableString(getKey(@"show_difficulty_graph"), @"Show difficulty graph on progress bar");
+
+ ///
+ /// "Show health display even when you can't fail"
+ ///
+ public static LocalisableString ShowHealthDisplayWhenCantFail => new TranslatableString(getKey(@"show_health_display_when_cant_fail"), @"Show health display even when you can't fail");
+
+ ///
+ /// "Fade playfield to red when health is low"
+ ///
+ public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low");
+
+ ///
+ /// "Always show key overlay"
+ ///
+ public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
+
+ ///
+ /// "Positional hitsounds"
+ ///
+ public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds");
+
+ ///
+ /// "Always play first combo break sound"
+ ///
+ public static LocalisableString AlwaysPlayFirstComboBreak => new TranslatableString(getKey(@"always_play_first_combo_break"), @"Always play first combo break sound");
+
+ ///
+ /// "Score display mode"
+ ///
+ public static LocalisableString ScoreDisplayMode => new TranslatableString(getKey(@"score_display_mode"), @"Score display mode");
+
+ ///
+ /// "Disable Windows key during gameplay"
+ ///
+ public static LocalisableString DisableWinKey => new TranslatableString(getKey(@"disable_win_key"), @"Disable Windows key during gameplay");
+
+ ///
+ /// "Mods"
+ ///
+ public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
+
+ ///
+ /// "Increase visibility of first object when visual impairment mods are enabled"
+ ///
+ public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs
new file mode 100644
index 0000000000..a60e4891f4
--- /dev/null
+++ b/osu.Game/Localisation/GeneralSettingsStrings.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GeneralSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GeneralSettings";
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralSectionHeader => new TranslatableString(getKey(@"general_section_header"), @"General");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageHeader => new TranslatableString(getKey(@"language_header"), @"Language");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageDropdown => new TranslatableString(getKey(@"language_dropdown"), @"Language");
+
+ ///
+ /// "Prefer metadata in original language"
+ ///
+ public static LocalisableString PreferOriginalMetadataLanguage => new TranslatableString(getKey(@"prefer_original"), @"Prefer metadata in original language");
+
+ ///
+ /// "Updates"
+ ///
+ public static LocalisableString UpdateHeader => new TranslatableString(getKey(@"update_header"), @"Updates");
+
+ ///
+ /// "Release stream"
+ ///
+ public static LocalisableString ReleaseStream => new TranslatableString(getKey(@"release_stream"), @"Release stream");
+
+ ///
+ /// "Check for updates"
+ ///
+ public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
+
+ ///
+ /// "Open osu! folder"
+ ///
+ public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
+
+ ///
+ /// "Change folder location..."
+ ///
+ public static LocalisableString ChangeFolderLocation => new TranslatableString(getKey(@"change_folder_location"), @"Change folder location...");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs
new file mode 100644
index 0000000000..0e384f983f
--- /dev/null
+++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs
@@ -0,0 +1,119 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GraphicsSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GraphicsSettings";
+
+ ///
+ /// "Graphics"
+ ///
+ public static LocalisableString GraphicsSectionHeader => new TranslatableString(getKey(@"graphics_section_header"), @"Graphics");
+
+ ///
+ /// "Renderer"
+ ///
+ public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
+
+ ///
+ /// "Frame limiter"
+ ///
+ public static LocalisableString FrameLimiter => new TranslatableString(getKey(@"frame_limiter"), @"Frame limiter");
+
+ ///
+ /// "Threading mode"
+ ///
+ public static LocalisableString ThreadingMode => new TranslatableString(getKey(@"threading_mode"), @"Threading mode");
+
+ ///
+ /// "Show FPS"
+ ///
+ public static LocalisableString ShowFPS => new TranslatableString(getKey(@"show_fps"), @"Show FPS");
+
+ ///
+ /// "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. "2x refresh rate" is recommended."
+ ///
+ public static LocalisableString UnlimitedFramesNote => new TranslatableString(getKey(@"unlimited_frames_note"), @"Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. ""2x refresh rate"" is recommended.");
+
+ ///
+ /// "Layout"
+ ///
+ public static LocalisableString LayoutHeader => new TranslatableString(getKey(@"layout_header"), @"Layout");
+
+ ///
+ /// "Screen mode"
+ ///
+ public static LocalisableString ScreenMode => new TranslatableString(getKey(@"screen_mode"), @"Screen mode");
+
+ ///
+ /// "Resolution"
+ ///
+ public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
+
+ ///
+ /// "UI scaling"
+ ///
+ public static LocalisableString UIScaling => new TranslatableString(getKey(@"ui_scaling"), @"UI scaling");
+
+ ///
+ /// "Screen scaling"
+ ///
+ public static LocalisableString ScreenScaling => new TranslatableString(getKey(@"screen_scaling"), @"Screen scaling");
+
+ ///
+ /// "Horizontal position"
+ ///
+ public static LocalisableString HorizontalPosition => new TranslatableString(getKey(@"horizontal_position"), @"Horizontal position");
+
+ ///
+ /// "Vertical position"
+ ///
+ public static LocalisableString VerticalPosition => new TranslatableString(getKey(@"vertical_position"), @"Vertical position");
+
+ ///
+ /// "Horizontal scale"
+ ///
+ public static LocalisableString HorizontalScale => new TranslatableString(getKey(@"horizontal_scale"), @"Horizontal scale");
+
+ ///
+ /// "Vertical scale"
+ ///
+ public static LocalisableString VerticalScale => new TranslatableString(getKey(@"vertical_scale"), @"Vertical scale");
+
+ ///
+ /// "Running without fullscreen mode will increase your input latency!"
+ ///
+ public static LocalisableString NotFullscreenNote => new TranslatableString(getKey(@"not_fullscreen_note"), @"Running without fullscreen mode will increase your input latency!");
+
+ ///
+ /// "Detail Settings"
+ ///
+ public static LocalisableString DetailSettingsHeader => new TranslatableString(getKey(@"detail_settings_header"), @"Detail Settings");
+
+ ///
+ /// "Storyboard / video"
+ ///
+ public static LocalisableString StoryboardVideo => new TranslatableString(getKey(@"storyboard_video"), @"Storyboard / video");
+
+ ///
+ /// "Hit lighting"
+ ///
+ public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
+
+ ///
+ /// "Screenshot format"
+ ///
+ public static LocalisableString ScreenshotFormat => new TranslatableString(getKey(@"screenshot_format"), @"Screenshot format");
+
+ ///
+ /// "Show menu cursor in screenshots"
+ ///
+ public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/InputSettingsStrings.cs b/osu.Game/Localisation/InputSettingsStrings.cs
new file mode 100644
index 0000000000..e46b4cecf3
--- /dev/null
+++ b/osu.Game/Localisation/InputSettingsStrings.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class InputSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.InputSettings";
+
+ ///
+ /// "Input"
+ ///
+ public static LocalisableString InputSectionHeader => new TranslatableString(getKey(@"input_section_header"), @"Input");
+
+ ///
+ /// "Global"
+ ///
+ public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectSection => new TranslatableString(getKey(@"song_select_section"), @"Song Select");
+
+ ///
+ /// "In Game"
+ ///
+ public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game");
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSection => new TranslatableString(getKey(@"audio_section"), @"Audio");
+
+ ///
+ /// "Editor"
+ ///
+ public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
+
+ ///
+ /// "Reset all bindings in section"
+ ///
+ public static LocalisableString ResetSectionButton => new TranslatableString(getKey(@"reset_section_button"), @"Reset all bindings in section");
+
+ ///
+ /// "key configuration"
+ ///
+ public static LocalisableString KeyBindingPanelHeader => new TranslatableString(getKey(@"key_binding_panel_header"), @"key configuration");
+
+ ///
+ /// "Customise your keys!"
+ ///
+ public static LocalisableString KeyBindingPanelDescription => new TranslatableString(getKey(@"key_binding_panel_description"), @"Customise your keys!");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
new file mode 100644
index 0000000000..a0e1a9ddab
--- /dev/null
+++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
@@ -0,0 +1,74 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class MaintenanceSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MaintenanceSettings";
+
+ ///
+ /// "Maintenance"
+ ///
+ public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance");
+
+ ///
+ /// "Select directory"
+ ///
+ public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
+
+ ///
+ /// "Import beatmaps from stable"
+ ///
+ public static LocalisableString ImportBeatmapsFromStable => new TranslatableString(getKey(@"import_beatmaps_from_stable"), @"Import beatmaps from stable");
+
+ ///
+ /// "Delete ALL beatmaps"
+ ///
+ public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
+
+ ///
+ /// "Import scores from stable"
+ ///
+ public static LocalisableString ImportScoresFromStable => new TranslatableString(getKey(@"import_scores_from_stable"), @"Import scores from stable");
+
+ ///
+ /// "Delete ALL scores"
+ ///
+ public static LocalisableString DeleteAllScores => new TranslatableString(getKey(@"delete_all_scores"), @"Delete ALL scores");
+
+ ///
+ /// "Import skins from stable"
+ ///
+ public static LocalisableString ImportSkinsFromStable => new TranslatableString(getKey(@"import_skins_from_stable"), @"Import skins from stable");
+
+ ///
+ /// "Delete ALL skins"
+ ///
+ public static LocalisableString DeleteAllSkins => new TranslatableString(getKey(@"delete_all_skins"), @"Delete ALL skins");
+
+ ///
+ /// "Import collections from stable"
+ ///
+ public static LocalisableString ImportCollectionsFromStable => new TranslatableString(getKey(@"import_collections_from_stable"), @"Import collections from stable");
+
+ ///
+ /// "Delete ALL collections"
+ ///
+ public static LocalisableString DeleteAllCollections => new TranslatableString(getKey(@"delete_all_collections"), @"Delete ALL collections");
+
+ ///
+ /// "Restore all hidden difficulties"
+ ///
+ public static LocalisableString RestoreAllHiddenDifficulties => new TranslatableString(getKey(@"restore_all_hidden_difficulties"), @"Restore all hidden difficulties");
+
+ ///
+ /// "Restore all recently deleted beatmaps"
+ ///
+ public static LocalisableString RestoreAllRecentlyDeletedBeatmaps => new TranslatableString(getKey(@"restore_all_recently_deleted_beatmaps"), @"Restore all recently deleted beatmaps");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
new file mode 100644
index 0000000000..111c068bbd
--- /dev/null
+++ b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class MultiplayerTeamResultsScreenStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MultiplayerTeamResultsScreen";
+
+ ///
+ /// "Team {0} wins!"
+ ///
+ public static LocalisableString TeamWins(string winner) => new TranslatableString(getKey(@"team_wins"), @"Team {0} wins!", winner);
+
+ ///
+ /// "The teams are tied!"
+ ///
+ public static LocalisableString TheTeamsAreTied => new TranslatableString(getKey(@"the_teams_are_tied"), @"The teams are tied!");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs
new file mode 100644
index 0000000000..6862f4ac2c
--- /dev/null
+++ b/osu.Game/Localisation/OnlineSettingsStrings.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class OnlineSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.OnlineSettings";
+
+ ///
+ /// "Online"
+ ///
+ public static LocalisableString OnlineSectionHeader => new TranslatableString(getKey(@"online_section_header"), @"Online");
+
+ ///
+ /// "Alerts and Privacy"
+ ///
+ public static LocalisableString AlertsAndPrivacyHeader => new TranslatableString(getKey(@"alerts_and_privacy_header"), @"Alerts and Privacy");
+
+ ///
+ /// "Show a notification when someone mentions your name"
+ ///
+ public static LocalisableString NotifyOnMentioned => new TranslatableString(getKey(@"notify_on_mentioned"), @"Show a notification when someone mentions your name");
+
+ ///
+ /// "Show a notification when you receive a private message"
+ ///
+ public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");
+
+ ///
+ /// "Integrations"
+ ///
+ public static LocalisableString IntegrationsHeader => new TranslatableString(getKey(@"integrations_header"), @"Integrations");
+
+ ///
+ /// "Discord Rich Presence"
+ ///
+ public static LocalisableString DiscordRichPresence => new TranslatableString(getKey(@"discord_rich_presence"), @"Discord Rich Presence");
+
+ ///
+ /// "Web"
+ ///
+ public static LocalisableString WebHeader => new TranslatableString(getKey(@"web_header"), @"Web");
+
+ ///
+ /// "Warn about opening external links"
+ ///
+ public static LocalisableString ExternalLinkWarning => new TranslatableString(getKey(@"external_link_warning"), @"Warn about opening external links");
+
+ ///
+ /// "Prefer downloads without video"
+ ///
+ public static LocalisableString PreferNoVideo => new TranslatableString(getKey(@"prefer_no_video"), @"Prefer downloads without video");
+
+ ///
+ /// "Automatically download beatmaps when spectating"
+ ///
+ public static LocalisableString AutomaticallyDownloadWhenSpectating => new TranslatableString(getKey(@"automatically_download_when_spectating"), @"Automatically download beatmaps when spectating");
+
+ ///
+ /// "Show explicit content in search results"
+ ///
+ public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs
new file mode 100644
index 0000000000..f22b4d6bf5
--- /dev/null
+++ b/osu.Game/Localisation/SkinSettingsStrings.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class SkinSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.SkinSettings";
+
+ ///
+ /// "Skin"
+ ///
+ public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
+
+ ///
+ /// "Skin layout editor"
+ ///
+ public static LocalisableString SkinLayoutEditor => new TranslatableString(getKey(@"skin_layout_editor"), @"Skin layout editor");
+
+ ///
+ /// "Gameplay cursor size"
+ ///
+ public static LocalisableString GameplayCursorSize => new TranslatableString(getKey(@"gameplay_cursor_size"), @"Gameplay cursor size");
+
+ ///
+ /// "Adjust gameplay cursor size based on current beatmap"
+ ///
+ public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
+
+ ///
+ /// "Beatmap skins"
+ ///
+ public static LocalisableString BeatmapSkins => new TranslatableString(getKey(@"beatmap_skins"), @"Beatmap skins");
+
+ ///
+ /// "Beatmap colours"
+ ///
+ public static LocalisableString BeatmapColours => new TranslatableString(getKey(@"beatmap_colours"), @"Beatmap colours");
+
+ ///
+ /// "Beatmap hitsounds"
+ ///
+ public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds");
+
+ ///
+ /// "Export selected skin"
+ ///
+ public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs
new file mode 100644
index 0000000000..4be403edb4
--- /dev/null
+++ b/osu.Game/Localisation/UserInterfaceStrings.cs
@@ -0,0 +1,114 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class UserInterfaceStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.UserInterface";
+
+ ///
+ /// "User Interface"
+ ///
+ public static LocalisableString UserInterfaceSectionHeader => new TranslatableString(getKey(@"user_interface_section_header"), @"User Interface");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Rotate cursor when dragging"
+ ///
+ public static LocalisableString CursorRotation => new TranslatableString(getKey(@"cursor_rotation"), @"Rotate cursor when dragging");
+
+ ///
+ /// "Menu cursor size"
+ ///
+ public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size");
+
+ ///
+ /// "Parallax"
+ ///
+ public static LocalisableString Parallax => new TranslatableString(getKey(@"parallax"), @"Parallax");
+
+ ///
+ /// "Hold-to-confirm activation time"
+ ///
+ public static LocalisableString HoldToConfirmActivationTime => new TranslatableString(getKey(@"hold_to_confirm_activation_time"), @"Hold-to-confirm activation time");
+
+ ///
+ /// "Main Menu"
+ ///
+ public static LocalisableString MainMenuHeader => new TranslatableString(getKey(@"main_menu_header"), @"Main Menu");
+
+ ///
+ /// "Interface voices"
+ ///
+ public static LocalisableString InterfaceVoices => new TranslatableString(getKey(@"interface_voices"), @"Interface voices");
+
+ ///
+ /// "osu! music theme"
+ ///
+ public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"osu! music theme");
+
+ ///
+ /// "Intro sequence"
+ ///
+ public static LocalisableString IntroSequence => new TranslatableString(getKey(@"intro_sequence"), @"Intro sequence");
+
+ ///
+ /// "Background source"
+ ///
+ public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
+
+ ///
+ /// "Seasonal backgrounds"
+ ///
+ public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
+
+ ///
+ /// "Changes to this setting will only apply with an active osu!supporter tag."
+ ///
+ public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active osu!supporter tag.");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectHeader => new TranslatableString(getKey(@"song_select_header"), @"Song Select");
+
+ ///
+ /// "Right mouse drag to absolute scroll"
+ ///
+ public static LocalisableString RightMouseScroll => new TranslatableString(getKey(@"right_mouse_scroll"), @"Right mouse drag to absolute scroll");
+
+ ///
+ /// "Show converted beatmaps"
+ ///
+ public static LocalisableString ShowConvertedBeatmaps => new TranslatableString(getKey(@"show_converted_beatmaps"), @"Show converted beatmaps");
+
+ ///
+ /// "Display beatmaps from"
+ ///
+ public static LocalisableString StarsMinimum => new TranslatableString(getKey(@"stars_minimum"), @"Display beatmaps from");
+
+ ///
+ /// "up to"
+ ///
+ public static LocalisableString StarsMaximum => new TranslatableString(getKey(@"stars_maximum"), @"up to");
+
+ ///
+ /// "Random selection algorithm"
+ ///
+ public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm");
+
+ ///
+ /// "no limit"
+ ///
+ public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs b/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
index 95a5d0acbd..b24669e6d5 100644
--- a/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
+++ b/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
@@ -9,16 +9,16 @@ namespace osu.Game.Online.API.Requests
{
public class MarkChannelAsReadRequest : APIRequest
{
- private readonly Channel channel;
- private readonly Message message;
+ public readonly Channel Channel;
+ public readonly Message Message;
public MarkChannelAsReadRequest(Channel channel, Message message)
{
- this.channel = channel;
- this.message = message;
+ Channel = channel;
+ Message = message;
}
- protected override string Target => $"chat/channels/{channel.Id}/mark-as-read/{message.Id}";
+ protected override string Target => $"chat/channels/{Channel.Id}/mark-as-read/{Message.Id}";
protected override WebRequest CreateWebRequest()
{
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 3136a3960d..1937019ef6 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -553,7 +553,7 @@ namespace osu.Game.Online.Chat
if (channel.LastMessageId == channel.LastReadId)
return;
- var message = channel.Messages.LastOrDefault();
+ var message = channel.Messages.FindLast(msg => !(msg is LocalMessage));
if (message == null)
return;
diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
index 064065ab00..8f16d22c4c 100644
--- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
@@ -31,6 +31,15 @@ namespace osu.Game.Online.Multiplayer
/// The user.
Task UserLeft(MultiplayerRoomUser user);
+ ///
+ /// Signals that a user has been kicked from the room.
+ ///
+ ///
+ /// This will also be sent to the user that was kicked.
+ ///
+ /// The user.
+ Task UserKicked(MultiplayerRoomUser user);
+
///
/// Signal that the host of the room has changed.
///
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index 4607211cdf..2a0635c98c 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -389,6 +389,18 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
+ Task IMultiplayerClient.UserKicked(MultiplayerRoomUser user)
+ {
+ if (LocalUser == null)
+ return Task.CompletedTask;
+
+ if (user.Equals(LocalUser))
+ LeaveRoom();
+
+ // TODO: also inform users of the kick operation.
+ return ((IMultiplayerClient)this).UserLeft(user);
+ }
+
Task IMultiplayerClient.HostChanged(int userId)
{
if (Room == null)
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index 55477a9fc7..c38a648a6a 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -50,6 +50,7 @@ namespace osu.Game.Online.Multiplayer
connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged);
connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
+ connection.On(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked);
connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 3cfa2cc755..fb682e0909 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -64,6 +64,11 @@ namespace osu.Game
///
public class OsuGame : OsuGameBase, IKeyBindingHandler
{
+ ///
+ /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
+ ///
+ protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
+
public Toolbar Toolbar;
private ChatOverlay chatOverlay;
@@ -71,7 +76,7 @@ namespace osu.Game
private ChannelManager channelManager;
[NotNull]
- private readonly NotificationOverlay notifications = new NotificationOverlay();
+ protected readonly NotificationOverlay Notifications = new NotificationOverlay();
private BeatmapListingOverlay beatmapListing;
@@ -97,7 +102,7 @@ namespace osu.Game
private ScalingContainer screenContainer;
- private Container screenOffsetContainer;
+ protected Container ScreenOffsetContainer { get; private set; }
[Resolved]
private FrameworkConfigManager frameworkConfig { get; set; }
@@ -312,7 +317,7 @@ namespace osu.Game
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
- waitForReady(() => notifications, _ => notifications.Post(new SimpleNotification
+ waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
{
Text = @"This link type is not yet supported!",
Icon = FontAwesome.Solid.LifeRing,
@@ -611,12 +616,12 @@ namespace osu.Game
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
// todo: all archive managers should be able to be looped here.
- SkinManager.PostNotification = n => notifications.Post(n);
+ SkinManager.PostNotification = n => Notifications.Post(n);
- BeatmapManager.PostNotification = n => notifications.Post(n);
+ BeatmapManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
- ScoreManager.PostNotification = n => notifications.Post(n);
+ ScoreManager.PostNotification = n => Notifications.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First());
// make config aware of how to lookup skins for on-screen display purposes.
@@ -655,7 +660,7 @@ namespace osu.Game
ActionRequested = action => volume.Adjust(action),
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
},
- screenOffsetContainer = new Container
+ ScreenOffsetContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@@ -724,7 +729,7 @@ namespace osu.Game
loadComponentSingleFile(onScreenDisplay, Add, true);
- loadComponentSingleFile(notifications.With(d =>
+ loadComponentSingleFile(Notifications.With(d =>
{
d.GetToolbarHeight = () => ToolbarOffset;
d.Anchor = Anchor.TopRight;
@@ -733,7 +738,7 @@ namespace osu.Game
loadComponentSingleFile(new CollectionManager(Storage)
{
- PostNotification = n => notifications.Post(n),
+ PostNotification = n => Notifications.Post(n),
}, Add, true);
loadComponentSingleFile(stableImportManager, Add);
@@ -785,7 +790,7 @@ namespace osu.Game
Add(new MusicKeyBindingHandler());
// side overlays which cancel each other.
- var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
+ var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications };
foreach (var overlay in singleDisplaySideOverlays)
{
@@ -828,21 +833,6 @@ namespace osu.Game
{
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
};
-
- void updateScreenOffset()
- {
- float offset = 0;
-
- if (Settings.State.Value == Visibility.Visible)
- offset += Toolbar.HEIGHT / 2;
- if (notifications.State.Value == Visibility.Visible)
- offset -= Toolbar.HEIGHT / 2;
-
- screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
- }
-
- Settings.State.ValueChanged += _ => updateScreenOffset();
- notifications.State.ValueChanged += _ => updateScreenOffset();
}
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
@@ -874,7 +864,7 @@ namespace osu.Game
if (recentLogCount < short_term_display_limit)
{
- Schedule(() => notifications.Post(new SimpleErrorNotification
+ Schedule(() => Notifications.Post(new SimpleErrorNotification
{
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
@@ -882,7 +872,7 @@ namespace osu.Game
}
else if (recentLogCount == short_term_display_limit)
{
- Schedule(() => notifications.Post(new SimpleNotification
+ Schedule(() => Notifications.Post(new SimpleNotification
{
Icon = FontAwesome.Solid.EllipsisH,
Text = "Subsequent messages have been logged. Click to view log files.",
@@ -1023,9 +1013,18 @@ namespace osu.Game
{
base.UpdateAfterChildren();
- screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
+ ScreenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
+ var horizontalOffset = 0f;
+
+ if (Settings.IsLoaded && Settings.IsPresent)
+ horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
+ if (Notifications.IsLoaded && Notifications.IsPresent)
+ horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
+
+ ScreenOffsetContainer.X = horizontalOffset;
+
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
}
diff --git a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
index 4bbc3569fe..3aa9aa5ca5 100644
--- a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
@@ -139,19 +139,24 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
{
- // beatmapset may have changed.
- if (Preview != preview)
- return;
+ // Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
+ // See: https://github.com/ppy/osu-framework/issues/4692
+ Schedule(() =>
+ {
+ // beatmapset may have changed.
+ if (Preview != preview)
+ return;
- AddInternal(preview);
- loading = false;
- // make sure that the update of value of Playing (and the ensuing value change callbacks)
- // are marshaled back to the update thread.
- preview.Stopped += () => Schedule(() => playing.Value = false);
+ AddInternal(preview);
+ loading = false;
+ // make sure that the update of value of Playing (and the ensuing value change callbacks)
+ // are marshaled back to the update thread.
+ preview.Stopped += () => Schedule(() => playing.Value = false);
- // user may have changed their mind.
- if (playing.Value)
- attemptStart();
+ // user may have changed their mind.
+ if (playing.Value)
+ attemptStart();
+ });
});
}
else
diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
index 3f1034759e..757698e1aa 100644
--- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs
+++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
@@ -78,10 +78,10 @@ namespace osu.Game.Overlays.BeatmapSet
Direction = FillDirection.Horizontal,
Children = new[]
{
- length = new Statistic(FontAwesome.Regular.Clock, "Length") { Width = 0.25f },
- bpm = new Statistic(FontAwesome.Regular.Circle, "BPM") { Width = 0.25f },
- circleCount = new Statistic(FontAwesome.Regular.Circle, "Circle Count") { Width = 0.25f },
- sliderCount = new Statistic(FontAwesome.Regular.Circle, "Slider Count") { Width = 0.25f },
+ length = new Statistic(BeatmapStatisticsIconType.Length, "Length") { Width = 0.25f },
+ bpm = new Statistic(BeatmapStatisticsIconType.Bpm, "BPM") { Width = 0.25f },
+ circleCount = new Statistic(BeatmapStatisticsIconType.Circles, "Circle Count") { Width = 0.25f },
+ sliderCount = new Statistic(BeatmapStatisticsIconType.Sliders, "Slider Count") { Width = 0.25f },
},
};
}
@@ -104,7 +104,7 @@ namespace osu.Game.Overlays.BeatmapSet
set => this.value.Text = value;
}
- public Statistic(IconUsage icon, string name)
+ public Statistic(BeatmapStatisticsIconType icon, string name)
{
TooltipText = name;
RelativeSizeAxes = Axes.X;
@@ -133,8 +133,16 @@ namespace osu.Game.Overlays.BeatmapSet
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
- Icon = icon,
- Size = new Vector2(12),
+ Icon = FontAwesome.Regular.Circle,
+ Size = new Vector2(10),
+ Rotation = 0,
+ Colour = Color4Extensions.FromHex(@"f7dd55"),
+ },
+ new BeatmapStatisticIcon(icon)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ Size = new Vector2(10),
Colour = Color4Extensions.FromHex(@"f7dd55"),
Scale = new Vector2(0.8f),
},
diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
index f617b4fc82..508c8399b6 100644
--- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
@@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Changelog
public class ChangelogSupporterPromo : CompositeDrawable
{
private const float image_container_width = 164;
+ private const float heart_size = 75;
private readonly FillFlowContainer textContainer;
private readonly Container imageContainer;
@@ -134,18 +135,30 @@ namespace osu.Game.Overlays.Changelog
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
+ Margin = new MarginPadding { Bottom = 28 },
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"),
},
- new Sprite
+ new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- Width = 75,
- Height = 75,
+ Size = new Vector2(heart_size),
Margin = new MarginPadding { Top = 70 },
- Texture = textures.Get(@"Online/supporter-heart"),
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = colour.Pink,
+ Radius = 10,
+ Roundness = heart_size / 2,
+ },
+ Child = new Sprite
+ {
+ Size = new Vector2(heart_size),
+ Texture = textures.Get(@"Online/supporter-heart"),
+ },
},
};
}
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index 513fabf52a..fe8d6f0178 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -14,6 +14,9 @@ using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading;
using osu.Game.Users;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Comments
{
@@ -147,7 +150,7 @@ namespace osu.Game.Overlays.Comments
private void refetchComments()
{
- clearComments();
+ ClearComments();
getComments();
}
@@ -160,50 +163,125 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
scheduledCommentsLoad?.Cancel();
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
- request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res));
+ request.Success += res => scheduledCommentsLoad = Schedule(() => OnSuccess(res));
api.PerformAsync(request);
}
- private void clearComments()
+ protected void ClearComments()
{
currentPage = 1;
deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true;
content.Clear();
+ CommentDictionary.Clear();
}
- private void onSuccess(CommentBundle response)
+ protected readonly Dictionary CommentDictionary = new Dictionary();
+
+ protected void OnSuccess(CommentBundle response)
{
- loadCancellation = new CancellationTokenSource();
+ commentCounter.Current.Value = response.Total;
- LoadComponentAsync(new CommentsPage(response)
+ if (!response.Comments.Any())
{
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- Type = { BindTarget = type },
- CommentableId = { BindTarget = id }
- }, loaded =>
+ content.Add(new NoCommentsPlaceholder());
+ moreButton.Hide();
+ return;
+ }
+
+ AppendComments(response);
+ }
+
+ ///
+ /// Appends retrieved comments to the subtree rooted of comments in this page.
+ ///
+ /// The bundle of comments to add.
+ protected void AppendComments([NotNull] CommentBundle bundle)
+ {
+ var topLevelComments = new List();
+ var orphaned = new List();
+
+ foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
{
- content.Add(loaded);
+ // Exclude possible duplicated comments.
+ if (CommentDictionary.ContainsKey(comment.Id))
+ continue;
- deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
+ addNewComment(comment);
+ }
- if (response.HasMore)
+ // Comments whose parents were seen later than themselves can now be added.
+ foreach (var o in orphaned)
+ addNewComment(o);
+
+ if (topLevelComments.Any())
+ {
+ LoadComponentsAsync(topLevelComments, loaded =>
{
- int loadedTopLevelComments = 0;
- content.Children.OfType().ForEach(p => loadedTopLevelComments += p.Children.OfType().Count());
+ content.AddRange(loaded);
- moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
- moreButton.IsLoading = false;
+ deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
+
+ if (bundle.HasMore)
+ {
+ int loadedTopLevelComments = 0;
+ content.Children.OfType().ForEach(p => loadedTopLevelComments++);
+
+ moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
+ moreButton.IsLoading = false;
+ }
+ else
+ {
+ moreButton.Hide();
+ }
+ }, (loadCancellation = new CancellationTokenSource()).Token);
+ }
+
+ void addNewComment(Comment comment)
+ {
+ var drawableComment = getDrawableComment(comment);
+
+ if (comment.ParentId == null)
+ {
+ // Comments that have no parent are added as top-level comments to the flow.
+ topLevelComments.Add(drawableComment);
+ }
+ else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
+ {
+ // The comment's parent has already been seen, so the parent<-> child links can be added.
+ comment.ParentComment = parentDrawable.Comment;
+ parentDrawable.Replies.Add(drawableComment);
}
else
{
- moreButton.Hide();
+ // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
+ // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
+ orphaned.Add(comment);
}
+ }
+ }
- commentCounter.Current.Value = response.Total;
- }, loadCancellation.Token);
+ private DrawableComment getDrawableComment(Comment comment)
+ {
+ if (CommentDictionary.TryGetValue(comment.Id, out var existing))
+ return existing;
+
+ return CommentDictionary[comment.Id] = new DrawableComment(comment)
+ {
+ ShowDeleted = { BindTarget = ShowDeleted },
+ Sort = { BindTarget = Sort },
+ RepliesRequested = onCommentRepliesRequested
+ };
+ }
+
+ private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
+ {
+ var req = new GetCommentsRequest(id.Value, type.Value, Sort.Value, page, drawableComment.Comment.Id);
+
+ req.Success += response => Schedule(() => AppendComments(response));
+
+ api.PerformAsync(req);
}
protected override void Dispose(bool isDisposing)
@@ -212,5 +290,30 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
base.Dispose(isDisposing);
}
+
+ private class NoCommentsPlaceholder : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ Height = 80;
+ RelativeSizeAxes = Axes.X;
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Margin = new MarginPadding { Left = 50 },
+ Text = @"No comments yet."
+ }
+ });
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs
deleted file mode 100644
index 9b146b0a7d..0000000000
--- a/osu.Game/Overlays/Comments/CommentsPage.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Framework.Bindables;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics.Sprites;
-using System.Linq;
-using osu.Game.Online.API.Requests;
-using osu.Game.Online.API;
-using System.Collections.Generic;
-using JetBrains.Annotations;
-
-namespace osu.Game.Overlays.Comments
-{
- public class CommentsPage : CompositeDrawable
- {
- public readonly BindableBool ShowDeleted = new BindableBool();
- public readonly Bindable Sort = new Bindable();
- public readonly Bindable Type = new Bindable();
- public readonly BindableLong CommentableId = new BindableLong();
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
- private readonly CommentBundle commentBundle;
- private FillFlowContainer flow;
-
- public CommentsPage(CommentBundle commentBundle)
- {
- this.commentBundle = commentBundle;
- }
-
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background5
- },
- flow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- }
- });
-
- if (!commentBundle.Comments.Any())
- {
- flow.Add(new NoCommentsPlaceholder());
- return;
- }
-
- AppendComments(commentBundle);
- }
-
- private DrawableComment getDrawableComment(Comment comment)
- {
- if (CommentDictionary.TryGetValue(comment.Id, out var existing))
- return existing;
-
- return CommentDictionary[comment.Id] = new DrawableComment(comment)
- {
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- RepliesRequested = onCommentRepliesRequested
- };
- }
-
- private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
- {
- var request = new GetCommentsRequest(CommentableId.Value, Type.Value, Sort.Value, page, drawableComment.Comment.Id);
-
- request.Success += response => Schedule(() => AppendComments(response));
-
- api.PerformAsync(request);
- }
-
- protected readonly Dictionary CommentDictionary = new Dictionary();
-
- ///
- /// Appends retrieved comments to the subtree rooted of comments in this page.
- ///
- /// The bundle of comments to add.
- protected void AppendComments([NotNull] CommentBundle bundle)
- {
- var orphaned = new List();
-
- foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
- {
- // Exclude possible duplicated comments.
- if (CommentDictionary.ContainsKey(comment.Id))
- continue;
-
- addNewComment(comment);
- }
-
- // Comments whose parents were seen later than themselves can now be added.
- foreach (var o in orphaned)
- addNewComment(o);
-
- void addNewComment(Comment comment)
- {
- var drawableComment = getDrawableComment(comment);
-
- if (comment.ParentId == null)
- {
- // Comments that have no parent are added as top-level comments to the flow.
- flow.Add(drawableComment);
- }
- else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
- {
- // The comment's parent has already been seen, so the parent<-> child links can be added.
- comment.ParentComment = parentDrawable.Comment;
- parentDrawable.Replies.Add(drawableComment);
- }
- else
- {
- // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
- // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
- orphaned.Add(comment);
- }
- }
- }
-
- private class NoCommentsPlaceholder : CompositeDrawable
- {
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- Height = 80;
- RelativeSizeAxes = Axes.X;
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background4
- },
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 50 },
- Text = @"No comments yet."
- }
- });
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs
index b26e17b34c..e3956089c2 100644
--- a/osu.Game/Overlays/NotificationOverlay.cs
+++ b/osu.Game/Overlays/NotificationOverlay.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Overlays
public LocalisableString Title => NotificationsStrings.HeaderTitle;
public LocalisableString Description => NotificationsStrings.HeaderDescription;
- private const float width = 320;
+ public const float WIDTH = 320;
public const float TRANSITION_LENGTH = 600;
@@ -38,7 +38,8 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- Width = width;
+ X = WIDTH;
+ Width = WIDTH;
RelativeSizeAxes = Axes.Y;
Children = new Drawable[]
@@ -152,7 +153,7 @@ namespace osu.Game.Overlays
markAllRead();
- this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint);
+ this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
index d64f176468..501f1b86b8 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
@@ -8,12 +8,13 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class AudioDevicesSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Devices";
+ protected override LocalisableString Header => AudioSettingsStrings.AudioDevicesHeader;
[Resolved]
private AudioManager audio { get; set; }
@@ -78,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
protected override LocalisableString GenerateItemText(string item)
- => string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
+ => string.IsNullOrEmpty(item) ? CommonStrings.Default : base.GenerateItemText(item);
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
index 7f2e377c83..9345d3fcc7 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
@@ -1,17 +1,22 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class OffsetSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Offset Adjustment";
+ protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
+
+ public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "universal", "uo", "timing" });
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,13 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Audio offset",
+ LabelText = AudioSettingsStrings.AudioOffset,
Current = config.GetBindable(OsuSetting.AudioOffset),
KeyboardStep = 1f
},
new SettingsButton
{
- Text = "Offset wizard"
+ Text = AudioSettingsStrings.OffsetWizard
}
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
index 8f88b03471..00c1cb8f43 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class VolumeSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Volume";
+ protected override LocalisableString Header => AudioSettingsStrings.VolumeHeader;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config)
@@ -20,28 +21,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Master",
+ LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Master (window inactive)",
+ LabelText = AudioSettingsStrings.MasterVolumeInactive,
Current = config.GetBindable(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Effect",
+ LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Music",
+ LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
index 7072d8e63d..694da0529a 100644
--- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
@@ -3,15 +3,17 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Localisation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Audio;
namespace osu.Game.Overlays.Settings.Sections
{
public class AudioSection : SettingsSection
{
- public override string Header => "Audio";
+ public override LocalisableString Header => AudioSettingsStrings.AudioSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
index 2b868cab85..25e20911b8 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Screens;
+using osu.Game.Localisation;
using osu.Game.Screens.Import;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => DebugSettingsStrings.GeneralHeader;
[BackgroundDependencyLoader(true)]
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game)
@@ -21,18 +22,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsCheckbox
{
- LabelText = "Show log overlay",
+ LabelText = DebugSettingsStrings.ShowLogOverlay,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay)
},
new SettingsCheckbox
{
- LabelText = "Bypass front-to-back render pass",
+ LabelText = DebugSettingsStrings.BypassFrontToBackPass,
Current = config.GetBindable(DebugSetting.BypassFrontToBackPass)
}
};
Add(new SettingsButton
{
- Text = "Import files",
+ Text = DebugSettingsStrings.ImportFiles,
Action = () => game?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
index bf7fb351c0..07fb0aca5a 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class MemorySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Memory";
+ protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
[BackgroundDependencyLoader]
private void load(FrameworkDebugConfigManager config, GameHost host)
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsButton
{
- Text = "Clear all caches",
+ Text = DebugSettingsStrings.ClearAllCaches,
Action = host.Collect
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
index 44d4088972..aa85ec920c 100644
--- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Debug;
namespace osu.Game.Overlays.Settings.Sections
{
public class DebugSection : SettingsSection
{
- public override string Header => "Debug";
+ public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 353292606f..3a0265e453 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => GameplaySettingsStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -21,62 +22,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsSlider
{
- LabelText = "Background dim",
+ LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Background blur",
+ LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
- LabelText = "Lighten playfield during breaks",
+ LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable(OsuSetting.LightenDuringBreaks)
},
new SettingsEnumDropdown
{
- LabelText = "HUD overlay visibility mode",
+ LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable(OsuSetting.HUDVisibilityMode)
},
new SettingsCheckbox
{
- LabelText = "Show difficulty graph on progress bar",
+ LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
Current = config.GetBindable(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
- LabelText = "Show health display even when you can't fail",
+ LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
- LabelText = "Fade playfield to red when health is low",
+ LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow),
},
new SettingsCheckbox
{
- LabelText = "Always show key overlay",
+ LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable(OsuSetting.KeyOverlay)
},
new SettingsCheckbox
{
- LabelText = "Positional hitsounds",
+ LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable(OsuSetting.PositionalHitSounds)
},
new SettingsCheckbox
{
- LabelText = "Always play first combo break sound",
+ LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak)
},
new SettingsEnumDropdown
{
- LabelText = "Score display mode",
+ LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" }
},
@@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Add(new SettingsCheckbox
{
- LabelText = "Disable Windows key during gameplay",
+ LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable(OsuSetting.GameplayDisableWinKey)
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
index ec9ddde2da..dfa060e8d5 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
@@ -6,12 +6,13 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class ModsSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Mods";
+ protected override LocalisableString Header => GameplaySettingsStrings.ModsHeader;
public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" });
@@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsCheckbox
{
- LabelText = "Increase visibility of first object when visual impairment mods are enabled",
+ LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility,
Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility),
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index acb94a6a01..42d9d48d73 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -9,12 +9,14 @@ using osu.Game.Rulesets;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections
{
public class GameplaySection : SettingsSection
{
- public override string Header => "Gameplay";
+ public override LocalisableString Header => GameplaySettingsStrings.GameplaySectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
index c6c752e2fd..200618c469 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
private SettingsDropdown languageSelection;
private Bindable frameworkLocale;
- protected override LocalisableString Header => "Language";
+ protected override LocalisableString Header => GeneralSettingsStrings.LanguageHeader;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
@@ -27,11 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
languageSelection = new SettingsEnumDropdown
{
- LabelText = "Language",
+ LabelText = GeneralSettingsStrings.LanguageDropdown,
},
new SettingsCheckbox
{
- LabelText = "Prefer metadata in original language",
+ LabelText = GeneralSettingsStrings.PreferOriginalMetadataLanguage,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index dd20e1d7ef..aa37748653 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved(CanBeNull = true)]
private UpdateManager updateManager { get; set; }
- protected override LocalisableString Header => "Updates";
+ protected override LocalisableString Header => GeneralSettingsStrings.UpdateHeader;
private SettingsButton checkForUpdatesButton;
@@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsEnumDropdown
{
- LabelText = "Release stream",
+ LabelText = GeneralSettingsStrings.ReleaseStream,
Current = config.GetBindable(OsuSetting.ReleaseStream),
});
@@ -40,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(checkForUpdatesButton = new SettingsButton
{
- Text = "Check for updates",
+ Text = GeneralSettingsStrings.CheckUpdate,
Action = () =>
{
checkForUpdatesButton.Enabled.Value = false;
@@ -65,13 +66,13 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsButton
{
- Text = "Open osu! folder",
+ Text = GeneralSettingsStrings.OpenOsuFolder,
Action = storage.OpenInNativeExplorer,
});
Add(new SettingsButton
{
- Text = "Change folder location...",
+ Text = GeneralSettingsStrings.ChangeFolderLocation,
Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
index fefc3fe6a7..87e9f34833 100644
--- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.General;
namespace osu.Game.Overlays.Settings.Sections
{
public class GeneralSection : SettingsSection
{
- public override string Header => "General";
+ public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
index f889cfca0f..20b1d8d801 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class DetailSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Detail Settings";
+ protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,22 +20,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsCheckbox
{
- LabelText = "Storyboard / Video",
+ LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
- LabelText = "Hit Lighting",
+ LabelText = GraphicsSettingsStrings.HitLighting,
Current = config.GetBindable(OsuSetting.HitLighting)
},
new SettingsEnumDropdown
{
- LabelText = "Screenshot format",
+ LabelText = GraphicsSettingsStrings.ScreenshotFormat,
Current = config.GetBindable(OsuSetting.ScreenshotFormat)
},
new SettingsCheckbox
{
- LabelText = "Show menu cursor in screenshots",
+ LabelText = GraphicsSettingsStrings.ShowCursorInScreenshots,
Current = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 91208cb78a..124b3b804c 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -16,13 +16,14 @@ using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class LayoutSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Layout";
+ protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
private FillFlowContainer> scalingSettings;
@@ -67,20 +68,20 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
windowModeDropdown = new SettingsDropdown
{
- LabelText = "Screen mode",
+ LabelText = GraphicsSettingsStrings.ScreenMode,
ItemSource = windowModes,
Current = config.GetBindable(FrameworkSetting.WindowMode),
},
resolutionDropdown = new ResolutionSettingsDropdown
{
- LabelText = "Resolution",
+ LabelText = GraphicsSettingsStrings.Resolution,
ShowsDefaultIndicator = false,
ItemSource = resolutions,
Current = sizeFullscreen
},
new SettingsSlider
{
- LabelText = "UI Scaling",
+ LabelText = GraphicsSettingsStrings.UIScaling,
TransferValueOnCommit = true,
Current = osuConfig.GetBindable(OsuSetting.UIScale),
KeyboardStep = 0.01f,
@@ -88,7 +89,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
},
new SettingsEnumDropdown
{
- LabelText = "Screen Scaling",
+ LabelText = GraphicsSettingsStrings.ScreenScaling,
Current = osuConfig.GetBindable(OsuSetting.Scaling),
Keywords = new[] { "scale", "letterbox" },
},
@@ -104,28 +105,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsSlider
{
- LabelText = "Horizontal position",
+ LabelText = GraphicsSettingsStrings.HorizontalPosition,
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical position",
+ LabelText = GraphicsSettingsStrings.VerticalPosition,
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Horizontal scale",
+ LabelText = GraphicsSettingsStrings.HorizontalScale,
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical scale",
+ LabelText = GraphicsSettingsStrings.VerticalScale,
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@@ -145,9 +146,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
updateResolutionDropdown();
- const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!";
-
- windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty;
+ windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
}, true);
windowModes.BindCollectionChanged((sender, args) =>
@@ -245,7 +244,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
- return "Default";
+ return CommonStrings.Default;
return $"{item.Width}x{item.Height}";
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index 2210c7911e..653f30a018 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -7,12 +7,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class RendererSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Renderer";
+ protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
private SettingsEnumDropdown frameLimiterDropdown;
@@ -25,17 +26,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
// TODO: this needs to be a custom dropdown at some point
frameLimiterDropdown = new SettingsEnumDropdown
{
- LabelText = "Frame limiter",
+ LabelText = GraphicsSettingsStrings.FrameLimiter,
Current = config.GetBindable(FrameworkSetting.FrameSync)
},
new SettingsEnumDropdown
{
- LabelText = "Threading mode",
+ LabelText = GraphicsSettingsStrings.ThreadingMode,
Current = config.GetBindable(FrameworkSetting.ExecutionMode)
},
new SettingsCheckbox
{
- LabelText = "Show FPS",
+ LabelText = GraphicsSettingsStrings.ShowFPS,
Current = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay)
},
};
@@ -47,9 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
frameLimiterDropdown.Current.BindValueChanged(limit =>
{
- const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended.";
-
- frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty;
+ frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
index 4ade48031f..fd0718f9f2 100644
--- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Graphics;
namespace osu.Game.Overlays.Settings.Sections
{
public class GraphicsSection : SettingsSection
{
- public override string Header => "Graphics";
+ public override LocalisableString Header => GraphicsSettingsStrings.GraphicsSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
index 9898a50320..3350ff4eaa 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = FontAwesome.Solid.Globe
};
- public override string Header => "Global";
+ public override LocalisableString Header => InputSettingsStrings.GlobalKeyBindingHeader;
public GlobalKeyBindingsSection(GlobalActionContainer manager)
{
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
public SongSelectKeyBindingSubsection(GlobalActionContainer manager)
: base(null)
@@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class InGameKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "In Game";
+ protected override LocalisableString Header => InputSettingsStrings.InGameSection;
public InGameKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -61,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Audio";
+ protected override LocalisableString Header => InputSettingsStrings.AudioSection;
public AudioControlKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -72,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class EditorKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Editor";
+ protected override LocalisableString Header => InputSettingsStrings.EditorSection;
public EditorKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
index 7cdc739b7c..67f1bb8d3e 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
@@ -4,13 +4,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public class KeyBindingPanel : SettingsSubPanel
{
- protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
+ protected override Drawable CreateHeader() => new SettingsHeader(InputSettingsStrings.KeyBindingPanelHeader, InputSettingsStrings.KeyBindingPanelDescription);
[BackgroundDependencyLoader(permitNulls: true)]
private void load(RulesetStore rulesets, GlobalActionContainer global)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
index 6e018597be..c38c516f21 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -385,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public CancelButton()
{
- Text = "Cancel";
+ Text = CommonStrings.Cancel;
Size = new Vector2(80, 20);
}
}
@@ -394,7 +395,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public ClearButton()
{
- Text = "Clear";
+ Text = CommonStrings.Clear;
Size = new Vector2(80, 20);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
index d65684fd37..ef5ccae1a0 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
@@ -10,6 +10,7 @@ using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
+using osu.Game.Localisation;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Input
@@ -64,7 +65,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader]
private void load()
{
- Text = "Reset all bindings in section";
+ Text = InputSettingsStrings.ResetSectionButton;
RelativeSizeAxes = Axes.X;
Width = 0.5f;
Anchor = Anchor.TopCentre;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
index 81a4d7eccd..5246051a4a 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets;
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = OsuIcon.Hot
};
- public override string Header => ruleset.Name;
+ public override LocalisableString Header => ruleset.Name;
private readonly RulesetInfo ruleset;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
index c7342c251d..b8b86d9069 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -9,9 +10,11 @@ using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Threading;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Localisation;
+using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -19,6 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
private readonly ITabletHandler tabletHandler;
+ private readonly Bindable enabled = new BindableBool(true);
+
private readonly Bindable areaOffset = new Bindable();
private readonly Bindable areaSize = new Bindable();
private readonly IBindable tablet = new Bindable();
@@ -52,7 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private FillFlowContainer mainSettings;
- private OsuSpriteText noTabletMessage;
+ private FillFlowContainer noTabletMessage;
protected override LocalisableString Header => TabletSettingsStrings.Tablet;
@@ -62,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuColour colours)
{
Children = new Drawable[]
{
@@ -71,14 +76,41 @@ namespace osu.Game.Overlays.Settings.Sections.Input
LabelText = CommonStrings.Enabled,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- Current = tabletHandler.Enabled
+ Current = enabled,
},
- noTabletMessage = new OsuSpriteText
+ noTabletMessage = new FillFlowContainer
{
- Text = TabletSettingsStrings.NoTabletDetected,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS },
+ Spacing = new Vector2(5f),
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = TabletSettingsStrings.NoTabletDetected,
+ },
+ new SettingsNoticeText(colours)
+ {
+ TextAnchor = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ }.With(t =>
+ {
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
+ {
+ t.NewLine();
+ t.AddText("If your tablet is not detected, please read ");
+ t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows
+ ? @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Windows-FAQ"
+ : @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ");
+ t.AddText(" for troubleshooting steps.");
+ }
+ }),
+ }
},
mainSettings = new FillFlowContainer
{
@@ -164,6 +196,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
base.LoadComplete();
+ enabled.BindTo(tabletHandler.Enabled);
+ enabled.BindValueChanged(_ => Scheduler.AddOnce(updateVisibility));
+
rotation.BindTo(tabletHandler.Rotation);
areaOffset.BindTo(tabletHandler.AreaOffset);
@@ -209,7 +244,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
tablet.BindTo(tabletHandler.Tablet);
tablet.BindValueChanged(val =>
{
- Scheduler.AddOnce(toggleVisibility);
+ Scheduler.AddOnce(updateVisibility);
var tab = val.NewValue;
@@ -229,19 +264,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, true);
}
- private void toggleVisibility()
+ private void updateVisibility()
{
- bool tabletFound = tablet.Value != null;
-
- if (!tabletFound)
- {
- mainSettings.Hide();
- noTabletMessage.Show();
- return;
- }
-
- mainSettings.Show();
+ mainSettings.Hide();
noTabletMessage.Hide();
+
+ if (!tabletHandler.Enabled.Value)
+ return;
+
+ if (tablet.Value != null)
+ mainSettings.Show();
+ else
+ noTabletMessage.Show();
}
private void applyAspectRatio(BindableNumber sizeChanged)
diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs
index 366f39388a..d282ba5318 100644
--- a/osu.Game/Overlays/Settings/Sections/InputSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Game.Overlays.Settings.Sections
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private readonly KeyBindingPanel keyConfig;
- public override string Header => "Input";
+ public override LocalisableString Header => InputSettingsStrings.InputSectionHeader;
[Resolved]
private GameHost host { get; set; }
@@ -95,7 +96,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
new SettingsCheckbox
{
- LabelText = "Enabled",
+ LabelText = CommonStrings.Enabled,
Current = handler.Enabled
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
index 5392ba5d93..e509cac2f1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
@@ -14,6 +14,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
@@ -104,7 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Origin = Anchor.Centre,
Width = 300,
Margin = new MarginPadding(10),
- Text = "Select directory",
+ Text = MaintenanceSettingsStrings.SelectDirectory,
Action = () => OnSelection(directorySelector.CurrentPath.Value)
},
}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index b9a408b1f8..803c8332c1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Scoring;
using osu.Game.Skinning;
@@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importBeatmapsButton = new SettingsButton
{
- Text = "Import beatmaps from stable",
+ Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
Action = () =>
{
importBeatmapsButton.Enabled.Value = false;
@@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteBeatmapsButton = new DangerousSettingsButton
{
- Text = "Delete ALL beatmaps",
+ Text = MaintenanceSettingsStrings.DeleteAllBeatmaps,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importScoresButton = new SettingsButton
{
- Text = "Import scores from stable",
+ Text = MaintenanceSettingsStrings.ImportScoresFromStable,
Action = () =>
{
importScoresButton.Enabled.Value = false;
@@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteScoresButton = new DangerousSettingsButton
{
- Text = "Delete ALL scores",
+ Text = MaintenanceSettingsStrings.DeleteAllScores,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importSkinsButton = new SettingsButton
{
- Text = "Import skins from stable",
+ Text = MaintenanceSettingsStrings.ImportSkinsFromStable,
Action = () =>
{
importSkinsButton.Enabled.Value = false;
@@ -100,7 +101,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteSkinsButton = new DangerousSettingsButton
{
- Text = "Delete ALL skins",
+ Text = MaintenanceSettingsStrings.DeleteAllSkins,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -117,7 +118,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importCollectionsButton = new SettingsButton
{
- Text = "Import collections from stable",
+ Text = MaintenanceSettingsStrings.ImportCollectionsFromStable,
Action = () =>
{
importCollectionsButton.Enabled.Value = false;
@@ -128,7 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(new DangerousSettingsButton
{
- Text = "Delete ALL collections",
+ Text = MaintenanceSettingsStrings.DeleteAllCollections,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll));
@@ -140,7 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
restoreButton = new SettingsButton
{
- Text = "Restore all hidden difficulties",
+ Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties,
Action = () =>
{
restoreButton.Enabled.Value = false;
@@ -153,7 +154,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
},
undeleteButton = new SettingsButton
{
- Text = "Restore all recently deleted beatmaps",
+ Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps,
Action = () =>
{
undeleteButton.Enabled.Value = false;
diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
index 73c88b8e71..fa0c06167b 100644
--- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
@@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osuTK;
@@ -10,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
public class MaintenanceSection : SettingsSection
{
- public override string Header => "Maintenance";
+ public override LocalisableString Header => MaintenanceSettingsStrings.MaintenanceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
index 3a2de2ee36..351a32c72e 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class AlertsAndPrivacySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Alerts and Privacy";
+ protected override LocalisableString Header => OnlineSettingsStrings.AlertsAndPrivacyHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Show a notification when someone mentions your name",
+ LabelText = OnlineSettingsStrings.NotifyOnMentioned,
Current = config.GetBindable(OsuSetting.NotifyOnUsernameMentioned)
},
new SettingsCheckbox
{
- LabelText = "Show a notification when you receive a private message",
+ LabelText = OnlineSettingsStrings.NotifyOnPrivateMessage,
Current = config.GetBindable(OsuSetting.NotifyOnPrivateMessage)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
index f2012f0d9c..0207f2fd01 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class IntegrationSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Integrations";
+ protected override LocalisableString Header => OnlineSettingsStrings.IntegrationsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsEnumDropdown
{
- LabelText = "Discord Rich Presence",
+ LabelText = OnlineSettingsStrings.DiscordRichPresence,
Current = config.GetBindable(OsuSetting.DiscordRichPresence)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
index 89e7b096f3..e864260cc6 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class WebSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Web";
+ protected override LocalisableString Header => OnlineSettingsStrings.WebHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,24 +20,24 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Warn about opening external links",
+ LabelText = OnlineSettingsStrings.ExternalLinkWarning,
Current = config.GetBindable(OsuSetting.ExternalLinkWarning)
},
new SettingsCheckbox
{
- LabelText = "Prefer downloads without video",
+ LabelText = OnlineSettingsStrings.PreferNoVideo,
Keywords = new[] { "no-video" },
Current = config.GetBindable(OsuSetting.PreferNoVideo)
},
new SettingsCheckbox
{
- LabelText = "Automatically download beatmaps when spectating",
+ LabelText = OnlineSettingsStrings.AutomaticallyDownloadWhenSpectating,
Keywords = new[] { "spectator" },
Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating),
},
new SettingsCheckbox
{
- LabelText = "Show explicit content in search results",
+ LabelText = OnlineSettingsStrings.ShowExplicitContent,
Keywords = new[] { "nsfw", "18+", "offensive" },
Current = config.GetBindable(OsuSetting.ShowOnlineExplicitContent),
}
diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
index 680d11f7da..8b523b90b9 100644
--- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Online;
namespace osu.Game.Overlays.Settings.Sections
{
public class OnlineSection : SettingsSection
{
- public override string Header => "Online";
+ public override LocalisableString Header => OnlineSettingsStrings.OnlineSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 9f3543d059..e0d8252930 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -13,6 +13,7 @@ using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK;
@@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private SkinSettingsDropdown skinDropdown;
- public override string Header => "Skin";
+ public override LocalisableString Header => SkinSettingsStrings.SkinSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
@@ -69,34 +70,34 @@ namespace osu.Game.Overlays.Settings.Sections
skinDropdown = new SkinSettingsDropdown(),
new SettingsButton
{
- Text = "Skin layout editor",
+ Text = SkinSettingsStrings.SkinLayoutEditor,
Action = () => skinEditor?.Toggle(),
},
new ExportSkinButton(),
new SettingsSlider
{
- LabelText = "Gameplay cursor size",
+ LabelText = SkinSettingsStrings.GameplayCursorSize,
Current = config.GetBindable(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Adjust gameplay cursor size based on current beatmap",
+ LabelText = SkinSettingsStrings.AutoCursorSize,
Current = config.GetBindable(OsuSetting.AutoCursorSize)
},
new SettingsCheckbox
{
- LabelText = "Beatmap skins",
+ LabelText = SkinSettingsStrings.BeatmapSkins,
Current = config.GetBindable(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
- LabelText = "Beatmap colours",
+ LabelText = SkinSettingsStrings.BeatmapColours,
Current = config.GetBindable(OsuSetting.BeatmapColours)
},
new SettingsCheckbox
{
- LabelText = "Beatmap hitsounds",
+ LabelText = SkinSettingsStrings.BeatmapHitsounds,
Current = config.GetBindable(OsuSetting.BeatmapHitsounds)
},
};
@@ -200,7 +201,7 @@ namespace osu.Game.Overlays.Settings.Sections
[BackgroundDependencyLoader]
private void load()
{
- Text = "Export selected skin";
+ Text = SkinSettingsStrings.ExportSkinButton;
Action = export;
currentSkin = skins.CurrentSkin.GetBoundCopy();
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
index 4b26645ef3..0afbed5df5 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => UserInterfaceStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Rotate cursor when dragging",
+ LabelText = UserInterfaceStrings.CursorRotation,
Current = config.GetBindable(OsuSetting.CursorRotation)
},
new SettingsSlider
{
- LabelText = "Menu cursor size",
+ LabelText = UserInterfaceStrings.MenuCursorSize,
Current = config.GetBindable(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Parallax",
+ LabelText = UserInterfaceStrings.Parallax,
Current = config.GetBindable(OsuSetting.MenuParallax)
},
new SettingsSlider
{
- LabelText = "Hold-to-confirm activation time",
+ LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
Current = config.GetBindable(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50
},
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
index 81bbcbb54a..40485a070c 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Users;
@@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class MainMenuSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Main Menu";
+ protected override LocalisableString Header => UserInterfaceStrings.MainMenuHeader;
private IBindable user;
@@ -28,27 +29,27 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Interface voices",
+ LabelText = UserInterfaceStrings.InterfaceVoices,
Current = config.GetBindable(OsuSetting.MenuVoice)
},
new SettingsCheckbox
{
- LabelText = "osu! music theme",
+ LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable(OsuSetting.MenuMusic)
},
new SettingsEnumDropdown
{
- LabelText = "Intro sequence",
+ LabelText = UserInterfaceStrings.IntroSequence,
Current = config.GetBindable(OsuSetting.IntroSequence),
},
backgroundSourceDropdown = new SettingsEnumDropdown
{
- LabelText = "Background source",
+ LabelText = UserInterfaceStrings.BackgroundSource,
Current = config.GetBindable(OsuSetting.MenuBackgroundSource),
},
new SettingsEnumDropdown
{
- LabelText = "Seasonal backgrounds",
+ LabelText = UserInterfaceStrings.SeasonalBackgrounds,
Current = config.GetBindable(OsuSetting.SeasonalBackgroundMode),
}
};
@@ -60,9 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
user.BindValueChanged(u =>
{
- const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag.";
-
- backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? not_supporter_note : string.Empty;
+ backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? UserInterfaceStrings.NotSupporterNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
index 587155eb0d..6290046987 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
@@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private Bindable minStars;
private Bindable maxStars;
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => UserInterfaceStrings.SongSelectHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -31,31 +32,31 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Right mouse drag to absolute scroll",
+ LabelText = UserInterfaceStrings.RightMouseScroll,
Current = config.GetBindable(OsuSetting.SongSelectRightMouseScroll),
},
new SettingsCheckbox
{
- LabelText = "Show converted beatmaps",
+ LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps),
},
new SettingsSlider
{
- LabelText = "Display beatmaps from",
+ LabelText = UserInterfaceStrings.StarsMinimum,
Current = config.GetBindable(OsuSetting.DisplayStarsMinimum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsSlider
{
- LabelText = "up to",
+ LabelText = UserInterfaceStrings.StarsMaximum,
Current = config.GetBindable(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsEnumDropdown
{
- LabelText = "Random selection algorithm",
+ LabelText = UserInterfaceStrings.RandomSelectionAlgorithm,
Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm),
}
};
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private class MaximumStarsSlider : StarsSlider
{
- public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText;
+ public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText;
}
private class StarsSlider : OsuSliderBar
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
index 718fea5f2b..6228c4c99a 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.UserInterface;
namespace osu.Game.Overlays.Settings.Sections
{
public class UserInterfaceSection : SettingsSection
{
- public override string Header => "User Interface";
+ public override LocalisableString Header => UserInterfaceStrings.UserInterfaceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index bd17c02af9..ef2027fdab 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -73,13 +73,7 @@ namespace osu.Game.Overlays.Settings
return;
// construct lazily for cases where the label is not needed (may be provided by the Control).
- FlowContent.Add(warningText = new OsuTextFlowContainer
- {
- Colour = colours.Yellow,
- Margin = new MarginPadding { Bottom = 5 },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- });
+ FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } });
}
warningText.Alpha = hasValue ? 0 : 1;
diff --git a/osu.Game/Overlays/Settings/SettingsNoticeText.cs b/osu.Game/Overlays/Settings/SettingsNoticeText.cs
new file mode 100644
index 0000000000..76ecf7edd4
--- /dev/null
+++ b/osu.Game/Overlays/Settings/SettingsNoticeText.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+
+namespace osu.Game.Overlays.Settings
+{
+ public class SettingsNoticeText : LinkFlowContainer
+ {
+ public SettingsNoticeText(OsuColour colours)
+ : base(s => s.Colour = colours.Yellow)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs
index 4143605c28..f993a46dc6 100644
--- a/osu.Game/Overlays/Settings/SettingsSection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSection.cs
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -19,10 +20,10 @@ namespace osu.Game.Overlays.Settings
protected override Container Content => FlowContent;
public abstract Drawable CreateIcon();
- public abstract string Header { get; }
+ public abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
- public virtual IEnumerable FilterTerms => new[] { Header };
+ public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
private const int header_size = 26;
private const int margin = 20;
diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs
index df32424b67..4aa9360452 100644
--- a/osu.Game/Overlays/Settings/SettingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Overlays.Settings
protected abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
+
+ // FilterTerms should contains both original string and localised string for user to search.
+ // Since LocalisableString is unable to get original string at this time (2021-08-14),
+ // only call .ToString() to use localised one.
+ // TODO: Update here when FilterTerms accept LocalisableString.
public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
public bool MatchingFilter
diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs
index 54b780615d..55e8aee266 100644
--- a/osu.Game/Overlays/SettingsOverlay.cs
+++ b/osu.Game/Overlays/SettingsOverlay.cs
@@ -9,7 +9,6 @@ using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
using osuTK.Graphics;
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Localisation;
@@ -38,6 +37,8 @@ namespace osu.Game.Overlays
private readonly List subPanels = new List();
+ private SettingsSubPanel lastOpenedSubPanel;
+
protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
protected override Drawable CreateFooter() => new SettingsFooter();
@@ -46,21 +47,21 @@ namespace osu.Game.Overlays
{
}
- public override bool AcceptsFocus => subPanels.All(s => s.State.Value != Visibility.Visible);
+ public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden;
private T createSubPanel(T subPanel)
where T : SettingsSubPanel
{
subPanel.Depth = 1;
subPanel.Anchor = Anchor.TopRight;
- subPanel.State.ValueChanged += subPanelStateChanged;
+ subPanel.State.ValueChanged += e => subPanelStateChanged(subPanel, e);
subPanels.Add(subPanel);
return subPanel;
}
- private void subPanelStateChanged(ValueChangedEvent state)
+ private void subPanelStateChanged(SettingsSubPanel panel, ValueChangedEvent state)
{
switch (state.NewValue)
{
@@ -68,7 +69,9 @@ namespace osu.Game.Overlays
Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint);
SectionsContainer.FadeOut(300, Easing.OutQuint);
- ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint);
+ ContentContainer.MoveToX(-PANEL_WIDTH, 500, Easing.OutQuint);
+
+ lastOpenedSubPanel = panel;
break;
case Visibility.Hidden:
@@ -80,7 +83,7 @@ namespace osu.Game.Overlays
}
}
- protected override float ExpandedPosition => subPanels.Any(s => s.State.Value == Visibility.Visible) ? -WIDTH : base.ExpandedPosition;
+ protected override float ExpandedPosition => lastOpenedSubPanel?.State.Value == Visibility.Visible ? -PANEL_WIDTH : base.ExpandedPosition;
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index eae828c142..f1c41c4b50 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -28,7 +28,15 @@ namespace osu.Game.Overlays
private const float sidebar_width = Sidebar.DEFAULT_WIDTH;
- public const float WIDTH = 400;
+ ///
+ /// The width of the settings panel content, excluding the sidebar.
+ ///
+ public const float PANEL_WIDTH = 400;
+
+ ///
+ /// The full width of the settings panel, including the sidebar.
+ ///
+ public const float WIDTH = sidebar_width + PANEL_WIDTH;
protected Container ContentContainer;
@@ -64,7 +72,8 @@ namespace osu.Game.Overlays
{
InternalChild = ContentContainer = new NonMaskedContent
{
- Width = WIDTH,
+ X = -WIDTH + ExpandedPosition,
+ Width = PANEL_WIDTH,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
index 067657159b..186514e868 100644
--- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
+++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
@@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Mods
{
// This is required as SettingsItem relies heavily on this bindable for internal use.
// The actual update flow is done via the bindable provided in the constructor.
- public Bindable Current { get; set; } = new Bindable();
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
public SliderControl(BindableNumber currentNumber)
{
diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs
index c0d7bae2b2..4425ece513 100644
--- a/osu.Game/Rulesets/Mods/ModFailCondition.cs
+++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -11,9 +13,12 @@ namespace osu.Game.Rulesets.Mods
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
+ [SettingSource("Restart on fail", "Automatically restarts when failed.")]
+ public BindableBool Restart { get; } = new BindableBool();
+
public virtual bool PerformFail() => true;
- public virtual bool RestartOnFail => true;
+ public virtual bool RestartOnFail => Restart.Value;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs
index 187a4d8e23..9016a24f8d 100644
--- a/osu.Game/Rulesets/Mods/ModPerfect.cs
+++ b/osu.Game/Rulesets/Mods/ModPerfect.cs
@@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
+ protected ModPerfect()
+ {
+ Restart.Value = Restart.Default = true;
+ }
+
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index 4a1f1196a9..62b3d33069 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -15,7 +15,6 @@ using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
-using osu.Game.Skinning;
namespace osu.Game.Screens.Edit.Compose
{
@@ -73,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose
{
Debug.Assert(ruleset != null);
- return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content);
+ return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content);
}
#region Input Handling
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 61a3b0f5cc..e8d919311b 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Screens.Edit
// todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor);
- AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin));
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin()));
dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs(changeHandler);
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index 7de98e5e85..3402bf653a 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit
private readonly Bindable hasTiming = new Bindable();
[CanBeNull]
- public readonly ISkin BeatmapSkin;
+ public readonly EditorBeatmapSkin BeatmapSkin;
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
@@ -69,7 +69,8 @@ namespace osu.Game.Screens.Edit
public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
{
PlayableBeatmap = playableBeatmap;
- BeatmapSkin = beatmapSkin;
+ if (beatmapSkin is Skin skin)
+ BeatmapSkin = new EditorBeatmapSkin(skin);
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);
diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs
new file mode 100644
index 0000000000..429df85904
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs
@@ -0,0 +1,59 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// A beatmap skin which is being edited.
+ ///
+ public class EditorBeatmapSkin : ISkin
+ {
+ public event Action BeatmapSkinChanged;
+
+ ///
+ /// The combo colours of this skin.
+ /// If empty, the default combo colours will be used.
+ ///
+ public readonly BindableList ComboColours;
+
+ private readonly Skin skin;
+
+ public EditorBeatmapSkin(Skin skin)
+ {
+ this.skin = skin;
+
+ ComboColours = new BindableList();
+ if (skin.Configuration.ComboColours != null)
+ ComboColours.AddRange(skin.Configuration.ComboColours.Select(c => (Colour4)c));
+ ComboColours.BindCollectionChanged((_, __) => updateColours());
+ }
+
+ private void invokeSkinChanged() => BeatmapSkinChanged?.Invoke();
+
+ private void updateColours()
+ {
+ skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList();
+ invokeSkinChanged();
+ }
+
+ #region Delegated ISkin implementation
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
+ public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+
+ #endregion
+ }
+}
diff --git a/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs b/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs
new file mode 100644
index 0000000000..27563b5a0f
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+#nullable enable
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// A that fires when users have made a change to the beatmap skin
+ /// of the map being edited.
+ ///
+ public class EditorSkinProvidingContainer : RulesetSkinProvidingContainer
+ {
+ private readonly EditorBeatmapSkin? beatmapSkin;
+
+ public EditorSkinProvidingContainer(EditorBeatmap editorBeatmap)
+ : base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap, editorBeatmap.BeatmapSkin)
+ {
+ beatmapSkin = editorBeatmap.BeatmapSkin;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (beatmapSkin != null)
+ beatmapSkin.BeatmapSkinChanged += TriggerSourceChanged;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmapSkin != null)
+ beatmapSkin.BeatmapSkinChanged -= TriggerSourceChanged;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
index 6f92db98ee..d26856365e 100644
--- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
+++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
@@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
- protected override ISkin GetSkin() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs
index d7e16645f2..05d9855a24 100644
--- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs
+++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs
@@ -1,14 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Skinning;
-using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Setup
{
@@ -31,9 +27,8 @@ namespace osu.Game.Screens.Edit.Setup
}
};
- var colours = Beatmap.BeatmapSkin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value;
- if (colours != null)
- comboColours.Colours.AddRange(colours.Select(c => (Colour4)c));
+ if (Beatmap.BeatmapSkin != null)
+ comboColours.Colours.BindTo(Beatmap.BeatmapSkin.ComboColours);
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
index d8dfac496d..e2ba0b03b0 100644
--- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
@@ -10,12 +10,12 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public class OnlinePlayBackgroundSprite : OnlinePlayComposite
{
- private readonly BeatmapSetCoverType beatmapSetCoverType;
+ protected readonly BeatmapSetCoverType BeatmapSetCoverType;
private UpdateableBeatmapBackgroundSprite sprite;
public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
- this.beatmapSetCoverType = beatmapSetCoverType;
+ BeatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
@@ -33,6 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
}
- protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
+ protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs
index bf0a53cbb6..b0db9256f5 100644
--- a/osu.Game/Screens/OnlinePlay/Header.cs
+++ b/osu.Game/Screens/OnlinePlay/Header.cs
@@ -2,19 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay
{
@@ -22,52 +18,30 @@ namespace osu.Game.Screens.OnlinePlay
{
public const float HEIGHT = 80;
+ private readonly ScreenStack stack;
+ private readonly MultiHeaderTitle title;
+
public Header(string mainTitle, ScreenStack stack)
{
+ this.stack = stack;
+
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
+ Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING };
- HeaderBreadcrumbControl breadcrumbs;
- MultiHeaderTitle title;
-
- Children = new Drawable[]
+ Child = title = new MultiHeaderTitle(mainTitle)
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex(@"#1f1921"),
- },
- new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
- Children = new Drawable[]
- {
- title = new MultiHeaderTitle(mainTitle)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft,
- },
- breadcrumbs = new HeaderBreadcrumbControl(stack)
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft
- }
- },
- },
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
};
- breadcrumbs.Current.ValueChanged += screen =>
- {
- if (screen.NewValue is IOnlinePlaySubScreen onlineSubScreen)
- title.Screen = onlineSubScreen;
- };
-
- breadcrumbs.Current.TriggerChange();
+ // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to.
+ stack.ScreenPushed += (_, __) => updateSubScreenTitle();
+ stack.ScreenExited += (_, __) => updateSubScreenTitle();
}
+ private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen;
+
private class MultiHeaderTitle : CompositeDrawable
{
private const float spacing = 6;
@@ -75,9 +49,10 @@ namespace osu.Game.Screens.OnlinePlay
private readonly OsuSpriteText dot;
private readonly OsuSpriteText pageTitle;
+ [CanBeNull]
public IOnlinePlaySubScreen Screen
{
- set => pageTitle.Text = value.ShortTitle.Titleize();
+ set => pageTitle.Text = value?.ShortTitle.Titleize() ?? string.Empty;
}
public MultiHeaderTitle(string mainTitle)
@@ -125,35 +100,5 @@ namespace osu.Game.Screens.OnlinePlay
pageTitle.Colour = dot.Colour = colours.Yellow;
}
}
-
- private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
- {
- public HeaderBreadcrumbControl(ScreenStack stack)
- : base(stack)
- {
- RelativeSizeAxes = Axes.X;
- StripColour = Color4.Transparent;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- AccentColour = Color4Extensions.FromHex("#e35c99");
- }
-
- protected override TabItem CreateTabItem(IScreen value) => new HeaderBreadcrumbTabItem(value)
- {
- AccentColour = AccentColour
- };
-
- private class HeaderBreadcrumbTabItem : BreadcrumbTabItem
- {
- public HeaderBreadcrumbTabItem(IScreen value)
- : base(value)
- {
- Bar.Colour = Color4.Transparent;
- }
- }
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index 193fb0cf57..c8ecd65574 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -158,21 +158,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Children = new Drawable[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
- new BufferedContainer
+ new Box
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colours.Background5,
- },
- new OnlinePlayBackgroundSprite
- {
- RelativeSizeAxes = Axes.Both
- },
- }
+ Colour = colours.Background5,
+ },
+ new OnlinePlayBackgroundSprite
+ {
+ RelativeSizeAxes = Axes.Both
},
new Container
{
@@ -187,37 +180,29 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
CornerRadius = corner_radius,
Children = new Drawable[]
{
- // This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
- new BufferedContainer
+ new GridContainer
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ ColumnDimensions = new[]
{
- new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- ColumnDimensions = new[]
- {
- new Dimension(GridSizeMode.Relative, 0.2f)
- },
- Content = new[]
- {
- new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colours.Background5,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
- },
- }
- }
- },
+ new Dimension(GridSizeMode.Relative, 0.2f)
},
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Background5,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
+ },
+ }
+ }
},
new Container
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs
deleted file mode 100644
index e2f02fca68..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.UserInterface;
-using osu.Framework.Threading;
-using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
-using osuTK;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public abstract class FilterControl : CompositeDrawable
- {
- protected readonly FillFlowContainer Filters;
-
- [Resolved(CanBeNull = true)]
- private Bindable filter { get; set; }
-
- [Resolved]
- private IBindable ruleset { get; set; }
-
- private readonly SearchTextBox search;
- private readonly Dropdown statusDropdown;
-
- protected FilterControl()
- {
- RelativeSizeAxes = Axes.X;
- Height = 70;
-
- InternalChild = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(10),
- Children = new Drawable[]
- {
- search = new FilterSearchTextBox
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.X,
- Width = 0.6f,
- },
- Filters = new FillFlowContainer
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10),
- Child = statusDropdown = new SlimEnumDropdown
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.None,
- Width = 160,
- }
- },
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- filter ??= new Bindable();
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- search.Current.BindValueChanged(_ => updateFilterDebounced());
- ruleset.BindValueChanged(_ => UpdateFilter());
- statusDropdown.Current.BindValueChanged(_ => UpdateFilter(), true);
- }
-
- private ScheduledDelegate scheduledFilterUpdate;
-
- private void updateFilterDebounced()
- {
- scheduledFilterUpdate?.Cancel();
- scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
- }
-
- protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
-
- private void updateFilter()
- {
- scheduledFilterUpdate?.Cancel();
-
- var criteria = CreateCriteria();
- criteria.SearchString = search.Current.Value;
- criteria.Status = statusDropdown.Current.Value;
- criteria.Ruleset = ruleset.Value;
-
- filter.Value = criteria;
- }
-
- protected virtual FilterCriteria CreateCriteria() => new FilterCriteria();
-
- public bool HoldFocus
- {
- get => search.HoldFocus;
- set => search.HoldFocus = value;
- }
-
- public void TakeFocus() => search.TakeFocus();
-
- private class FilterSearchTextBox : SearchTextBox
- {
- [BackgroundDependencyLoader]
- private void load()
- {
- BackgroundUnfocused = OsuColour.Gray(0.06f);
- BackgroundFocused = OsuColour.Gray(0.12f);
- }
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs
deleted file mode 100644
index bbf34d3893..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.UserInterface;
-using osu.Game.Graphics.UserInterface;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public class PlaylistsFilterControl : FilterControl
- {
- private readonly Dropdown categoryDropdown;
-
- public PlaylistsFilterControl()
- {
- Filters.Add(categoryDropdown = new SlimEnumDropdown
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.None,
- Width = 160,
- });
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- categoryDropdown.Current.BindValueChanged(_ => UpdateFilter());
- }
-
- protected override FilterCriteria CreateCriteria()
- {
- var criteria = base.CreateCriteria();
-
- switch (categoryDropdown.Current.Value)
- {
- case PlaylistsCategory.Normal:
- criteria.Category = "normal";
- break;
-
- case PlaylistsCategory.Spotlight:
- criteria.Category = "spotlight";
- break;
- }
-
- return criteria;
- }
-
- private enum PlaylistsCategory
- {
- Any,
- Normal,
- Spotlight
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 5e5863c7c4..46d9850fde 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -140,7 +140,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
roomFlow.Remove(toRemove);
- selectedRoom.Value = null;
+ // selection may have a lease due to being in a sub screen.
+ if (!selectedRoom.Disabled)
+ selectedRoom.Value = null;
}
}
@@ -152,7 +154,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override bool OnClick(ClickEvent e)
{
- selectedRoom.Value = null;
+ if (!selectedRoom.Disabled)
+ selectedRoom.Value = null;
return base.OnClick(e);
}
@@ -214,6 +217,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void selectNext(int direction)
{
+ if (selectedRoom.Disabled)
+ return;
+
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
Room room;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index 122b30b1d2..e0e5cc415e 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@@ -9,18 +11,20 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
+using osu.Framework.Threading;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
@@ -41,7 +45,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private readonly IBindable initialRoomsReceived = new Bindable();
private readonly IBindable operationInProgress = new Bindable();
- private FilterControl filter;
private LoadingLayer loadingLayer;
[Resolved]
@@ -53,31 +56,37 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved(CanBeNull = true)]
private OngoingOperationTracker ongoingOperationTracker { get; set; }
+ [Resolved(CanBeNull = true)]
+ private Bindable filter { get; set; }
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
[CanBeNull]
private IDisposable joiningRoomOperation { get; set; }
private RoomsContainer roomsContainer;
+ private SearchTextBox searchTextBox;
+ private Dropdown statusDropdown;
+
+ [CanBeNull]
+ private LeasedBindable selectionLease;
[BackgroundDependencyLoader]
private void load()
{
+ filter ??= new Bindable(new FilterCriteria());
+
OsuScrollContainer scrollContainer;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
- new Box
- {
- RelativeSizeAxes = Axes.X,
- Height = 100,
- Colour = Color4.Black,
- Alpha = 0.5f,
- },
+ loadingLayer = new LoadingLayer(true),
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
- Top = 20,
Left = WaveOverlayContainer.WIDTH_PADDING,
Right = WaveOverlayContainer.WIDTH_PADDING,
},
@@ -86,26 +95,50 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
- new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
+ new Dimension(GridSizeMode.Absolute, 25),
new Dimension(GridSizeMode.Absolute, 20)
},
Content = new[]
{
+ new Drawable[]
+ {
+ searchTextBox = new LoungeSearchTextBox
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.X,
+ Width = 0.6f,
+ },
+ },
new Drawable[]
{
new Container
{
- RelativeSizeAxes = Axes.X,
- Height = 70,
- Depth = -1,
+ RelativeSizeAxes = Axes.Both,
+ Depth = float.MinValue, // Contained filters should appear over the top of rooms.
Children = new Drawable[]
{
- filter = CreateFilterControl(),
Buttons.WithChild(CreateNewRoomButton().With(d =>
{
- d.Size = new Vector2(150, 25);
+ d.Anchor = Anchor.BottomLeft;
+ d.Origin = Anchor.BottomLeft;
+ d.Size = new Vector2(150, 37.5f);
d.Action = () => Open();
- }))
+ })),
+ new FillFlowContainer
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10),
+ ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
+ {
+ d.Anchor = Anchor.TopRight;
+ d.Origin = Anchor.TopRight;
+ }))
+ }
}
}
},
@@ -123,13 +156,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer()
},
- loadingLayer = new LoadingLayer(true),
}
},
}
}
},
- }
+ },
};
// scroll selected room into view on selection.
@@ -145,6 +177,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.LoadComplete();
+ searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
+ ruleset.BindValueChanged(_ => UpdateFilter());
+
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
@@ -153,13 +188,50 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
}
+
+ updateFilter();
}
- protected override void OnFocus(FocusEvent e)
+ #region Filtering
+
+ protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
+
+ private ScheduledDelegate scheduledFilterUpdate;
+
+ private void updateFilterDebounced()
{
- filter.TakeFocus();
+ scheduledFilterUpdate?.Cancel();
+ scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
}
+ private void updateFilter()
+ {
+ scheduledFilterUpdate?.Cancel();
+ filter.Value = CreateFilterCriteria();
+ }
+
+ protected virtual FilterCriteria CreateFilterCriteria() => new FilterCriteria
+ {
+ SearchString = searchTextBox.Current.Value,
+ Ruleset = ruleset.Value,
+ Status = statusDropdown.Current.Value
+ };
+
+ protected virtual IEnumerable CreateFilterControls()
+ {
+ statusDropdown = new SlimEnumDropdown
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = 160,
+ };
+
+ statusDropdown.Current.BindValueChanged(_ => UpdateFilter());
+
+ yield return statusDropdown;
+ }
+
+ #endregion
+
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
@@ -171,6 +243,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.OnResuming(last);
+ Debug.Assert(selectionLease != null);
+
+ selectionLease.Return();
+ selectionLease = null;
+
if (selectedRoom.Value?.RoomID.Value == null)
selectedRoom.Value = new Room();
@@ -191,14 +268,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.OnSuspending(next);
}
+ protected override void OnFocus(FocusEvent e)
+ {
+ searchTextBox.TakeFocus();
+ }
+
private void onReturning()
{
- filter.HoldFocus = true;
+ searchTextBox.HoldFocus = true;
}
private void onLeaving()
{
- filter.HoldFocus = false;
+ searchTextBox.HoldFocus = false;
// ensure any password prompt is dismissed.
this.HidePopover();
@@ -238,13 +320,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected virtual void OpenNewRoom(Room room)
{
- selectedRoom.Value = room;
+ selectionLease = selectedRoom.BeginLease(false);
+ Debug.Assert(selectionLease != null);
+ selectionLease.Value = room;
this.Push(CreateRoomSubScreen(room));
}
- protected abstract FilterControl CreateFilterControl();
-
protected abstract OsuButton CreateNewRoomButton();
///
@@ -262,5 +344,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
else
loadingLayer.Hide();
}
+
+ private class LoungeSearchTextBox : SearchTextBox
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ BackgroundUnfocused = OsuColour.Gray(0.06f);
+ BackgroundFocused = OsuColour.Gray(0.12f);
+ }
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index a53e253581..243d2abf74 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -8,8 +8,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -17,6 +19,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Match
{
@@ -61,8 +64,15 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected RoomSubScreen()
{
+ Padding = new MarginPadding { Top = Header.HEIGHT };
+
AddRangeInternal(new Drawable[]
{
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
+ },
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = SelectedItem }
@@ -250,5 +260,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}
+
+ public class UserModSelectButton : PurpleTriangleButton
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs
deleted file mode 100644
index 37e0fd109a..0000000000
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Screens.OnlinePlay.Multiplayer
-{
- public class MultiplayerFilterControl : FilterControl
- {
- protected override FilterCriteria CreateCriteria()
- {
- var criteria = base.CreateCriteria();
- criteria.Category = "realtime";
- return criteria;
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
index 621ff8881f..ad7882abc2 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
@@ -21,7 +21,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved]
private MultiplayerClient client { get; set; }
- protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
+ protected override FilterCriteria CreateFilterCriteria()
+ {
+ var criteria = base.CreateFilterCriteria();
+ criteria.Category = @"realtime";
+ return criteria;
+ }
protected override OsuButton CreateNewRoomButton() => new CreateMultiplayerMatchButton();
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 9fa19aaf21..1943ff668f 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -274,7 +274,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
isConnected.BindValueChanged(connected =>
{
if (!connected.NewValue)
- Schedule(this.Exit);
+ handleRoomLost();
}, true);
currentRoom.BindValueChanged(room =>
@@ -284,7 +284,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
// the room has gone away.
// this could mean something happened during the join process, or an external connection issue occurred.
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
- Schedule(this.Exit);
+ handleRoomLost();
}
}, true);
}
@@ -448,9 +448,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void onRoomUpdated()
{
+ // may happen if the client is kicked or otherwise removed from the room.
+ if (client.Room == null)
+ {
+ handleRoomLost();
+ return;
+ }
+
Scheduler.AddOnce(UpdateMods);
}
+ private void handleRoomLost() => Schedule(() =>
+ {
+ if (this.IsCurrentScreen())
+ this.Exit();
+ else
+ ValidForResume = false;
+ });
+
private void onLoadRequested()
{
if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable)
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index 3ba7b8b982..ca1a3710ab 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -181,7 +181,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override ResultsScreen CreateResults(ScoreInfo score)
{
Debug.Assert(RoomId.Value != null);
- return new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
+ return leaderboard.TeamScores.Count == 2
+ ? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
+ : new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs
new file mode 100644
index 0000000000..14a779dedf
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs
@@ -0,0 +1,152 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Localisation;
+using osu.Game.Online.Rooms;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer
+{
+ public class MultiplayerTeamResultsScreen : MultiplayerResultsScreen
+ {
+ private readonly SortedDictionary teamScores;
+
+ private Container winnerBackground;
+ private Drawable winnerText;
+
+ public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary teamScores)
+ : base(score, roomId, playlistItem)
+ {
+ if (teamScores.Count != 2)
+ throw new NotSupportedException(@"This screen currently only supports 2 teams");
+
+ this.teamScores = teamScores;
+ }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ const float winner_background_half_height = 250;
+
+ VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre;
+ VerticalScrollContent.Scale = new Vector2(0.9f);
+ VerticalScrollContent.Y = 75;
+
+ var redScore = teamScores.First().Value;
+ var blueScore = teamScores.Last().Value;
+
+ LocalisableString winner;
+ Colour4 winnerColour;
+
+ int comparison = redScore.Value.CompareTo(blueScore.Value);
+
+ if (comparison < 0)
+ {
+ // team name should eventually be coming from the multiplayer match state.
+ winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Blue");
+ winnerColour = colours.TeamColourBlue;
+ }
+ else if (comparison > 0)
+ {
+ // team name should eventually be coming from the multiplayer match state.
+ winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Red");
+ winnerColour = colours.TeamColourRed;
+ }
+ else
+ {
+ winner = MultiplayerTeamResultsScreenStrings.TheTeamsAreTied;
+ winnerColour = Colour4.White.Opacity(0.5f);
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new MatchScoreDisplay
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Team1Score = { BindTarget = redScore },
+ Team2Score = { BindTarget = blueScore },
+ },
+ winnerBackground = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Alpha = 0,
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = winner_background_half_height,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.BottomCentre,
+ Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0), Colour4.Black.Opacity(0.4f))
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = winner_background_half_height,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0.4f), Colour4.Black.Opacity(0))
+ }
+ }
+ },
+ (winnerText = new OsuSpriteText
+ {
+ Alpha = 0,
+ Font = OsuFont.Torus.With(size: 80, weight: FontWeight.Bold),
+ Text = winner,
+ Blending = BlendingParameters.Additive
+ }).WithEffect(new GlowEffect
+ {
+ Colour = winnerColour,
+ }).With(e =>
+ {
+ e.Anchor = Anchor.Centre;
+ e.Origin = Anchor.Centre;
+ })
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ using (BeginDelayedSequence(300))
+ {
+ const double fade_in_duration = 600;
+
+ winnerText.FadeInFromZero(fade_in_duration, Easing.InQuint);
+ winnerBackground.FadeInFromZero(fade_in_duration, Easing.InQuint);
+
+ winnerText
+ .ScaleTo(10)
+ .ScaleTo(1, 600, Easing.InQuad)
+ .Then()
+ .ScaleTo(1.02f, 1600, Easing.OutQuint)
+ .FadeOut(5000, Easing.InQuad);
+ winnerBackground.Delay(2200).FadeOut(2000);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index 1787480e1f..6f8c735b6e 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
@@ -83,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Colour = Color4Extensions.FromHex("#F7E65D"),
Alpha = 0
},
- new TeamDisplay(user),
+ new TeamDisplay(User),
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -168,12 +167,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Origin = Anchor.Centre,
Alpha = 0,
Margin = new MarginPadding(4),
- Action = () =>
- {
- Debug.Assert(user != null);
-
- Client.KickUser(user.Id);
- }
+ Action = () => Client.KickUser(User.UserID),
},
},
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
index 5a7073f9de..351b9b3673 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
@@ -11,7 +11,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
-using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@@ -19,16 +18,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
internal class TeamDisplay : MultiplayerRoomComposite
{
- private readonly User user;
+ private readonly MultiplayerRoomUser user;
+
private Drawable box;
[Resolved]
private OsuColour colours { get; set; }
- [Resolved]
- private MultiplayerClient client { get; set; }
-
- public TeamDisplay(User user)
+ public TeamDisplay(MultiplayerRoomUser user)
{
this.user = user;
@@ -61,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
}
};
- if (user.Id == client.LocalUser?.UserID)
+ if (Client.LocalUser?.Equals(user) == true)
{
InternalChild = new OsuClickableContainer
{
@@ -79,9 +76,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private void changeTeam()
{
- client.SendMatchRequest(new ChangeTeamRequest
+ Client.SendMatchRequest(new ChangeTeamRequest
{
- TeamID = ((client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
+ TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
});
}
@@ -93,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
// we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now.
- var userRoomState = Room?.Users.FirstOrDefault(u => u.UserID == user.Id)?.MatchState;
+ var userRoomState = Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
const double duration = 400;
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
index 86ce61f845..fd265e9978 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Framework.Screens;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Input;
@@ -21,8 +22,9 @@ using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
-using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay
{
@@ -71,9 +73,6 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved(CanBeNull = true)]
private OsuLogo logo { get; set; }
- private Drawable header;
- private Drawable headerBackground;
-
protected OnlinePlayScreen()
{
Anchor = Anchor.Centre;
@@ -104,42 +103,21 @@ namespace osu.Game.Screens.OnlinePlay
new Container
{
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = Header.HEIGHT },
- Children = new[]
+ Children = new Drawable[]
{
- header = new Container
+ new BeatmapBackgroundSprite
{
- RelativeSizeAxes = Axes.X,
- Height = 400,
- Children = new[]
- {
- headerBackground = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Width = 1.25f,
- Masking = true,
- Children = new Drawable[]
- {
- new HeaderBackgroundSprite
- {
- RelativeSizeAxes = Axes.X,
- Height = 400 // Keep a static height so the header doesn't change as it's resized between subscreens
- },
- }
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Bottom = -1 }, // 1px padding to avoid a 1px gap due to masking
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(backgroundColour.Opacity(0.5f), backgroundColour)
- },
- }
- }
+ RelativeSizeAxes = Axes.Both
},
- screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
+ },
+ screenStack = new OnlinePlaySubScreenStack
+ {
+ RelativeSizeAxes = Axes.Both
+ }
}
},
new Header(ScreenTitle, screenStack),
@@ -292,19 +270,6 @@ namespace osu.Game.Screens.OnlinePlay
private void subScreenChanged(IScreen lastScreen, IScreen newScreen)
{
- switch (newScreen)
- {
- case LoungeSubScreen _:
- header.Delay(OnlinePlaySubScreen.RESUME_TRANSITION_DELAY).ResizeHeightTo(400, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
- headerBackground.MoveToX(0, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
- break;
-
- case RoomSubScreen _:
- header.ResizeHeightTo(135, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
- headerBackground.MoveToX(-OnlinePlaySubScreen.X_SHIFT, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
- break;
- }
-
if (lastScreen is IOsuScreen lastOsuScreen)
Activity.UnbindFrom(lastOsuScreen.Activity);
@@ -335,13 +300,48 @@ namespace osu.Game.Screens.OnlinePlay
}
}
- private class HeaderBackgroundSprite : OnlinePlayBackgroundSprite
+ private class BeatmapBackgroundSprite : OnlinePlayBackgroundSprite
{
- protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
+ protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BlurredBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
- private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
+ public class BlurredBackgroundSprite : UpdateableBeatmapBackgroundSprite
{
- protected override double TransformDuration => 200;
+ public BlurredBackgroundSprite(BeatmapSetCoverType type)
+ : base(type)
+ {
+ }
+
+ protected override double LoadDelay => 200;
+
+ protected override Drawable CreateDrawable(BeatmapInfo model) =>
+ new BufferedLoader(base.CreateDrawable(model));
+ }
+
+ // This class is an unfortunate requirement due to `LongRunningLoad` requiring direct async loading.
+ // It means that if the web request fetching the beatmap background takes too long, it will suddenly appear.
+ internal class BufferedLoader : BufferedContainer
+ {
+ private readonly Drawable drawable;
+
+ public BufferedLoader(Drawable drawable)
+ {
+ this.drawable = drawable;
+
+ RelativeSizeAxes = Axes.Both;
+ BlurSigma = new Vector2(10);
+ FrameBufferScale = new Vector2(0.5f);
+ CacheDrawnFrameBuffer = true;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ LoadComponentAsync(drawable, d =>
+ {
+ Add(d);
+ ForceRedraw();
+ });
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
index be28de5c43..1502463022 100644
--- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
+++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs
@@ -59,6 +59,8 @@ namespace osu.Game.Screens.OnlinePlay
[BackgroundDependencyLoader]
private void load()
{
+ LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
+
initialBeatmap = Beatmap.Value;
initialRuleset = Ruleset.Value;
initialMods = Mods.Value.ToList();
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
index 4db1d6380d..eee4d4f407 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
@@ -16,7 +20,38 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved]
private IAPIProvider api { get; set; }
- protected override FilterControl CreateFilterControl() => new PlaylistsFilterControl();
+ private Dropdown categoryDropdown;
+
+ protected override IEnumerable CreateFilterControls()
+ {
+ categoryDropdown = new SlimEnumDropdown
+ {
+ RelativeSizeAxes = Axes.None,
+ Width = 160,
+ };
+
+ categoryDropdown.Current.BindValueChanged(_ => UpdateFilter());
+
+ return base.CreateFilterControls().Append(categoryDropdown);
+ }
+
+ protected override FilterCriteria CreateFilterCriteria()
+ {
+ var criteria = base.CreateFilterCriteria();
+
+ switch (categoryDropdown.Current.Value)
+ {
+ case PlaylistsCategory.Normal:
+ criteria.Category = @"normal";
+ break;
+
+ case PlaylistsCategory.Spotlight:
+ criteria.Category = @"spotlight";
+ break;
+ }
+
+ return criteria;
+ }
protected override OsuButton CreateNewRoomButton() => new CreatePlaylistsRoomButton();
@@ -30,5 +65,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
}
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new PlaylistsRoomSubScreen(room);
+
+ private enum PlaylistsCategory
+ {
+ Any,
+ Normal,
+ Spotlight
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 45aca24ab2..953c687087 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
index 63cb4f89f5..871555e5a3 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs
@@ -6,29 +6,58 @@ using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Caching;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.Containers;
using osu.Game.Users;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
- public class GameplayLeaderboard : FillFlowContainer
+ public class GameplayLeaderboard : CompositeDrawable
{
+ private readonly int maxPanels;
private readonly Cached sorting = new Cached();
public Bindable Expanded = new Bindable();
- public GameplayLeaderboard()
+ protected readonly FillFlowContainer Flow;
+
+ private bool requiresScroll;
+ private readonly OsuScrollContainer scroll;
+
+ private GameplayLeaderboardScore trackedScore;
+
+ ///
+ /// Create a new leaderboard.
+ ///
+ /// The maximum panels to show at once. Defines the maximum height of this component.
+ public GameplayLeaderboard(int maxPanels = 8)
{
+ this.maxPanels = maxPanels;
+
Width = GameplayLeaderboardScore.EXTENDED_WIDTH + GameplayLeaderboardScore.SHEAR_WIDTH;
- Direction = FillDirection.Vertical;
-
- Spacing = new Vector2(2.5f);
-
- LayoutDuration = 250;
- LayoutEasing = Easing.OutQuint;
+ InternalChildren = new Drawable[]
+ {
+ scroll = new InputDisabledScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = Flow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ X = GameplayLeaderboardScore.SHEAR_WIDTH,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(2.5f),
+ LayoutDuration = 450,
+ LayoutEasing = Easing.OutQuint,
+ }
+ }
+ };
}
protected override void LoadComplete()
@@ -46,26 +75,87 @@ namespace osu.Game.Screens.Play.HUD
/// Whether the player should be tracked on the leaderboard.
/// Set to true for the local player or a player whose replay is currently being played.
///
- public ILeaderboardScore AddPlayer([CanBeNull] User user, bool isTracked)
+ public ILeaderboardScore Add([CanBeNull] User user, bool isTracked)
{
var drawable = CreateLeaderboardScoreDrawable(user, isTracked);
+ if (isTracked)
+ {
+ if (trackedScore != null)
+ throw new InvalidOperationException("Cannot track more than one score.");
+
+ trackedScore = drawable;
+ }
+
drawable.Expanded.BindTo(Expanded);
- base.Add(drawable);
+ Flow.Add(drawable);
drawable.TotalScore.BindValueChanged(_ => sorting.Invalidate(), true);
- Height = Count * (GameplayLeaderboardScore.PANEL_HEIGHT + Spacing.Y);
+ int displayCount = Math.Min(Flow.Count, maxPanels);
+ Height = displayCount * (GameplayLeaderboardScore.PANEL_HEIGHT + Flow.Spacing.Y);
+ requiresScroll = displayCount != Flow.Count;
return drawable;
}
+ public void Clear()
+ {
+ Flow.Clear();
+ trackedScore = null;
+ scroll.ScrollToStart(false);
+ }
+
protected virtual GameplayLeaderboardScore CreateLeaderboardScoreDrawable(User user, bool isTracked) =>
new GameplayLeaderboardScore(user, isTracked);
- public sealed override void Add(GameplayLeaderboardScore drawable)
+ protected override void Update()
{
- throw new NotSupportedException($"Use {nameof(AddPlayer)} instead.");
+ base.Update();
+
+ if (requiresScroll && trackedScore != null)
+ {
+ float scrollTarget = scroll.GetChildPosInContent(trackedScore) + trackedScore.DrawHeight / 2 - scroll.DrawHeight / 2;
+ scroll.ScrollTo(scrollTarget, false);
+ }
+
+ const float panel_height = GameplayLeaderboardScore.PANEL_HEIGHT;
+
+ float fadeBottom = scroll.Current + scroll.DrawHeight;
+ float fadeTop = scroll.Current + panel_height;
+
+ if (scroll.Current <= 0) fadeTop -= panel_height;
+ if (!scroll.IsScrolledToEnd()) fadeBottom -= panel_height;
+
+ // logic is mostly shared with Leaderboard, copied here for simplicity.
+ foreach (var c in Flow.Children)
+ {
+ float topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, Flow).Y;
+ float bottomY = topY + panel_height;
+
+ bool requireTopFade = requiresScroll && topY <= fadeTop;
+ bool requireBottomFade = requiresScroll && bottomY >= fadeBottom;
+
+ if (!requireTopFade && !requireBottomFade)
+ c.Colour = Color4.White;
+ else if (topY > fadeBottom + panel_height || bottomY < fadeTop - panel_height)
+ c.Colour = Color4.Transparent;
+ else
+ {
+ if (requireBottomFade)
+ {
+ c.Colour = ColourInfo.GradientVertical(
+ Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / panel_height, 1)),
+ Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / panel_height, 1)));
+ }
+ else if (requiresScroll)
+ {
+ c.Colour = ColourInfo.GradientVertical(
+ Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / panel_height, 1)),
+ Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / panel_height, 1)));
+ }
+ }
+ }
}
private void sort()
@@ -73,15 +163,26 @@ namespace osu.Game.Screens.Play.HUD
if (sorting.IsValid)
return;
- var orderedByScore = this.OrderByDescending(i => i.TotalScore.Value).ToList();
+ var orderedByScore = Flow.OrderByDescending(i => i.TotalScore.Value).ToList();
- for (int i = 0; i < Count; i++)
+ for (int i = 0; i < Flow.Count; i++)
{
- SetLayoutPosition(orderedByScore[i], i);
+ Flow.SetLayoutPosition(orderedByScore[i], i);
orderedByScore[i].ScorePosition = i + 1;
}
sorting.Validate();
}
+
+ private class InputDisabledScrollContainer : OsuScrollContainer
+ {
+ public InputDisabledScrollContainer()
+ {
+ ScrollbarVisible = false;
+ }
+
+ public override bool HandlePositionalInput => false;
+ public override bool HandleNonPositionalInput => false;
+ }
}
}
diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
index 433bf78e9b..85cf9d1966 100644
--- a/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
+++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboardScore.cs
@@ -81,7 +81,10 @@ namespace osu.Game.Screens.Play.HUD
[CanBeNull]
public User User { get; }
- private readonly bool trackedPlayer;
+ ///
+ /// Whether this score is the local user or a replay player (and should be focused / always visible).
+ ///
+ public readonly bool Tracked;
private Container mainFillContainer;
@@ -97,11 +100,11 @@ namespace osu.Game.Screens.Play.HUD
/// Creates a new .
///
/// The score's player.
- /// Whether the player is the local user or a replay player.
- public GameplayLeaderboardScore([CanBeNull] User user, bool trackedPlayer)
+ /// Whether the player is the local user or a replay player.
+ public GameplayLeaderboardScore([CanBeNull] User user, bool tracked)
{
User = user;
- this.trackedPlayer = trackedPlayer;
+ Tracked = tracked;
AutoSizeAxes = Axes.X;
Height = PANEL_HEIGHT;
@@ -338,7 +341,7 @@ namespace osu.Game.Screens.Play.HUD
panelColour = BackgroundColour ?? Color4Extensions.FromHex("7fcc33");
textColour = TextColour ?? Color4.White;
}
- else if (trackedPlayer)
+ else if (Tracked)
{
widthExtension = true;
panelColour = BackgroundColour ?? Color4Extensions.FromHex("ffd966");
diff --git a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
index c77b872786..68e3f0df7d 100644
--- a/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/MatchScoreDisplay.cs
@@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -104,7 +105,7 @@ namespace osu.Game.Screens.Play.HUD
base.LoadComplete();
Team1Score.BindValueChanged(_ => updateScores());
- Team2Score.BindValueChanged(_ => updateScores());
+ Team2Score.BindValueChanged(_ => updateScores(), true);
}
private void updateScores()
@@ -171,6 +172,8 @@ namespace osu.Game.Screens.Play.HUD
=> displayedSpriteText.Font = winning
? OsuFont.Torus.With(weight: FontWeight.Bold, size: font_size, fixedWidth: true)
: OsuFont.Torus.With(weight: FontWeight.Regular, size: font_size * 0.8f, fixedWidth: true);
+
+ protected override LocalisableString FormatCount(double count) => count.ToLocalisableString(@"N0");
}
}
}
diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
index 3f9258930e..19cb6aeb50 100644
--- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
+++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play.HUD
var trackedUser = UserScores[user.Id];
- var leaderboardScore = AddPlayer(user, user.Id == api.LocalUser.Value.Id);
+ var leaderboardScore = Add(user, user.Id == api.LocalUser.Value.Id);
leaderboardScore.Accuracy.BindTo(trackedUser.Accuracy);
leaderboardScore.TotalScore.BindTo(trackedUser.Score);
leaderboardScore.Combo.BindTo(trackedUser.CurrentCombo);
@@ -184,7 +184,7 @@ namespace osu.Game.Screens.Play.HUD
continue;
if (TeamScores.TryGetValue(u.Team.Value, out var team))
- team.Value += (int)u.Score.Value;
+ team.Value += (int)Math.Round(u.Score.Value);
}
}
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index 2cf2555b3e..13df9fefa7 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -57,8 +57,6 @@ namespace osu.Game.Screens.Play
private Bindable configVisibilityMode;
- private readonly Container visibilityContainer;
-
private readonly BindableBool replayLoaded = new BindableBool();
private static bool hasShownNotificationOnce;
@@ -72,7 +70,7 @@ namespace osu.Game.Screens.Play
private readonly SkinnableTargetContainer mainComponents;
- private IEnumerable hideTargets => new Drawable[] { visibilityContainer, KeyCounter, topRightElements };
+ private IEnumerable hideTargets => new Drawable[] { mainComponents, KeyCounter, topRightElements };
public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods)
{
@@ -84,13 +82,9 @@ namespace osu.Game.Screens.Play
Children = new Drawable[]
{
CreateFailingLayer(),
- visibilityContainer = new Container
+ mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
{
RelativeSizeAxes = Axes.Both,
- Child = mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
- {
- RelativeSizeAxes = Axes.Both,
- },
},
topRightElements = new FillFlowContainer
{
diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs
index b458d7c17f..d44d1f2cc9 100644
--- a/osu.Game/Screens/Ranking/ResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/ResultsScreen.cs
@@ -40,6 +40,8 @@ namespace osu.Game.Screens.Ranking
protected ScorePanelList ScorePanelList { get; private set; }
+ protected VerticalScrollContainer VerticalScrollContent { get; private set; }
+
[Resolved(CanBeNull = true)]
private Player player { get; set; }
@@ -77,7 +79,7 @@ namespace osu.Game.Screens.Ranking
{
new Drawable[]
{
- new VerticalScrollContainer
+ VerticalScrollContent = new VerticalScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
@@ -343,7 +345,7 @@ namespace osu.Game.Screens.Ranking
{
}
- private class VerticalScrollContainer : OsuScrollContainer
+ protected class VerticalScrollContainer : OsuScrollContainer
{
protected override Container Content => content;
@@ -351,6 +353,8 @@ namespace osu.Game.Screens.Ranking
public VerticalScrollContainer()
{
+ Masking = false;
+
base.Content.Add(content = new Container { RelativeSizeAxes = Axes.X });
}
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 3779523094..5b4e077100 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -35,6 +35,7 @@ namespace osu.Game.Screens.Select
{
public class BeatmapInfoWedge : VisibilityContainer
{
+ public const float BORDER_THICKNESS = 2.5f;
private const float shear_width = 36.75f;
private static readonly Vector2 wedged_container_shear = new Vector2(shear_width / SongSelect.WEDGE_HEIGHT, 0);
@@ -59,7 +60,7 @@ namespace osu.Game.Screens.Select
Shear = wedged_container_shear;
Masking = true;
BorderColour = new Color4(221, 255, 255, 255);
- BorderThickness = 2.5f;
+ BorderThickness = BORDER_THICKNESS;
Alpha = 0;
EdgeEffect = new EdgeEffectParameters
{
diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs
index b9e912df8e..f47bc5f466 100644
--- a/osu.Game/Screens/Select/FilterCriteria.cs
+++ b/osu.Game/Screens/Select/FilterCriteria.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Select
set
{
searchText = value;
- SearchTerms = searchText.Split(new[] { ',', ' ', '!' }, StringSplitOptions.RemoveEmptyEntries).ToArray();
+ SearchTerms = searchText.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToArray();
SearchNumber = null;
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 270addc8e6..bb3df0d4e0 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -79,6 +79,8 @@ namespace osu.Game.Screens.Select
protected BeatmapCarousel Carousel { get; private set; }
+ protected Container LeftArea { get; private set; }
+
private BeatmapInfoWedge beatmapInfoWedge;
private DialogOverlay dialogOverlay;
@@ -186,12 +188,12 @@ namespace osu.Game.Screens.Select
{
new Drawable[]
{
- new Container
+ LeftArea = new Container
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
-
+ Padding = new MarginPadding { Top = left_area_padding },
Children = new Drawable[]
{
beatmapInfoWedge = new BeatmapInfoWedge
@@ -200,8 +202,8 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding
{
- Top = left_area_padding,
Right = left_area_padding,
+ Left = -BeatmapInfoWedge.BORDER_THICKNESS, // Hide the left border
},
},
new Container
@@ -210,7 +212,7 @@ namespace osu.Game.Screens.Select
Padding = new MarginPadding
{
Bottom = Footer.HEIGHT,
- Top = WEDGE_HEIGHT + left_area_padding,
+ Top = WEDGE_HEIGHT,
Left = left_area_padding,
Right = left_area_padding * 2,
},
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 30192182f3..16ac17546d 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using JetBrains.Annotations;
using osu.Framework.IO.Stores;
using osu.Game.Extensions;
@@ -21,12 +22,13 @@ namespace osu.Game.Skinning
: base(skin, new NamespacedResourceStore(resources.Resources, "Skins/Legacy"), resources, string.Empty)
{
Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255);
- Configuration.AddComboColours(
+ Configuration.CustomComboColours = new List
+ {
new Color4(255, 192, 0, 255),
new Color4(0, 202, 0, 255),
new Color4(18, 124, 255, 255),
new Color4(242, 24, 57, 255)
- );
+ };
Configuration.LegacyVersion = 2.7m;
}
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index 25a924c929..a18144246f 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -27,14 +27,14 @@ namespace osu.Game.Skinning
new Color4(242, 24, 57, 255),
};
- private readonly List comboColours = new List();
+ public List CustomComboColours { get; set; } = new List();
public IReadOnlyList ComboColours
{
get
{
- if (comboColours.Count > 0)
- return comboColours;
+ if (CustomComboColours.Count > 0)
+ return CustomComboColours;
if (AllowDefaultComboColoursFallback)
return DefaultComboColours;
@@ -43,7 +43,7 @@ namespace osu.Game.Skinning
}
}
- public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
+ void IHasComboColours.AddComboColours(params Color4[] colours) => CustomComboColours.AddRange(colours);
public Dictionary CustomColours { get; } = new Dictionary();
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 4935f7fc13..64f1ee4a7a 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -217,7 +217,7 @@ namespace osu.Game.Tests.Beatmaps
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
- protected override ISkin GetSkin() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
index 7ee6c519b7..bb5dd09e16 100644
--- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
+++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs
@@ -211,7 +211,7 @@ namespace osu.Game.Tests.Beatmaps
this.resources = resources;
}
- protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, resources);
+ protected internal override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, resources);
}
}
}
diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
index cdf6a9a2b4..27162b1d66 100644
--- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
+++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
HasColours = hasColours;
}
- protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, HasColours);
+ protected internal override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, HasColours);
}
protected class TestBeatmapSkin : LegacyBeatmapSkin
@@ -116,7 +116,7 @@ namespace osu.Game.Tests.Beatmaps
{
if (hasColours)
{
- Configuration.AddComboColours(Colours);
+ Configuration.CustomComboColours = Colours.ToList();
Configuration.CustomColours.Add("HyperDash", HYPER_DASH_COLOUR);
Configuration.CustomColours.Add("HyperDashAfterImage", HYPER_DASH_AFTER_IMAGE_COLOUR);
Configuration.CustomColours.Add("HyperDashFruit", HYPER_DASH_FRUIT_COLOUR);
@@ -145,7 +145,7 @@ namespace osu.Game.Tests.Beatmaps
{
if (hasCustomColours)
{
- Configuration.AddComboColours(Colours);
+ Configuration.CustomComboColours = Colours.ToList();
Configuration.CustomColours.Add("HyperDash", HYPER_DASH_COLOUR);
Configuration.CustomColours.Add("HyperDashAfterImage", HYPER_DASH_AFTER_IMAGE_COLOUR);
Configuration.CustomColours.Add("HyperDashFruit", HYPER_DASH_FRUIT_COLOUR);
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index bfce59c7de..19974701db 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Beatmaps
protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard();
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index a28b4140ca..f2da66d666 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
return roomUser;
}
- public void AddNullUser(int userId) => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(userId));
+ public void AddNullUser() => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
public void RemoveUser(User user)
{
@@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Debug.Assert(Room != null);
- return ((IMultiplayerClient)this).UserLeft(Room.Users.Single(u => u.UserID == userId));
+ return ((IMultiplayerClient)this).UserKicked(Room.Users.Single(u => u.UserID == userId));
}
public override async Task ChangeSettings(MultiplayerRoomSettings settings)
diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs
index d2941b5bd5..b73e81d0dd 100644
--- a/osu.Game/Tests/Visual/TestUserLookupCache.cs
+++ b/osu.Game/Tests/Visual/TestUserLookupCache.cs
@@ -10,10 +10,22 @@ namespace osu.Game.Tests.Visual
{
public class TestUserLookupCache : UserLookupCache
{
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User
+ ///
+ /// A special user ID which would return a for.
+ /// As a simulation to what a regular would return in the case of failing to fetch the user.
+ ///
+ public const int NULL_USER_ID = -1;
+
+ protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
{
- Id = lookup,
- Username = $"User {lookup}"
- });
+ if (lookup == NULL_USER_ID)
+ return Task.FromResult((User)null);
+
+ return Task.FromResult(new User
+ {
+ Id = lookup,
+ Username = $"User {lookup}"
+ });
+ }
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e6219fcb85..d4dba9330f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9904946363..7e514afe74 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -93,7 +93,7 @@
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 139ee02b96..d884ea31c5 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -304,6 +304,7 @@
AABB
API
BPM
+ FPS
GC
GL
GLSL