diff --git a/osu.Android.props b/osu.Android.props index b9b127309a..1774ea0bb4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -53,7 +53,7 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index b9294088f4..c34e1e1221 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png new file mode 100644 index 0000000000..3a6612378e Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png differ diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png new file mode 100644 index 0000000000..afb8698b2d Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png differ diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index 0ad72412fc..56e378d19e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 1eb913e900..070847c0c1 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 51c821a1e8..47b4258ad9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; using osu.Game.Tests.Visual; using osuTK; @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Catch.Tests { typeof(CatchHitObject), typeof(Fruit), + typeof(FruitPiece), typeof(Droplet), typeof(Banana), typeof(BananaShower), @@ -37,9 +38,51 @@ namespace osu.Game.Rulesets.Catch.Tests foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation))) AddStep($"show {rep}", () => SetContents(() => createDrawable(rep))); + + AddStep("show droplet", () => SetContents(createDrawableDroplet)); + + AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet)); } - private DrawableFruit createDrawable(FruitVisualRepresentation rep) + private Drawable createDrawableTinyDroplet() + { + var droplet = new TinyDroplet + { + StartTime = Clock.CurrentTime, + Scale = 1.5f, + }; + + return new DrawableTinyDroplet(droplet) + { + Anchor = Anchor.Centre, + RelativePositionAxes = Axes.None, + Position = Vector2.Zero, + Alpha = 1, + LifetimeStart = double.NegativeInfinity, + LifetimeEnd = double.PositiveInfinity, + }; + } + + private Drawable createDrawableDroplet() + { + var droplet = new Droplet + { + StartTime = Clock.CurrentTime, + Scale = 1.5f, + }; + + return new DrawableDroplet(droplet) + { + Anchor = Anchor.Centre, + RelativePositionAxes = Axes.None, + Position = Vector2.Zero, + Alpha = 1, + LifetimeStart = double.NegativeInfinity, + LifetimeEnd = double.PositiveInfinity, + }; + } + + private Drawable createDrawable(FruitVisualRepresentation rep) { Fruit fruit = new TestCatchFruit(rep) { diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index db52fbac1b..1a5d0f983b 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -34,9 +34,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps foreach (var obj in Beatmap.HitObjects.OfType()) { - obj.IndexInBeatmap = index++; + obj.IndexInBeatmap = index; + foreach (var nested in obj.NestedHitObjects.OfType()) + nested.IndexInBeatmap = index; + if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested) lastNested.LastInCombo = true; + + index++; } } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index e5c3647f99..b9d791fdb1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -21,6 +21,8 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch { @@ -141,6 +143,8 @@ namespace osu.Game.Rulesets.Catch public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); + public override ISkin CreateLegacySkinProvider(ISkinSource source) => new CatchLegacySkinTransformer(source); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); public int LegacyID => 2; diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs index 7e482d4045..02c045f363 100644 --- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs +++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs @@ -5,5 +5,11 @@ namespace osu.Game.Rulesets.Catch { public enum CatchSkinComponents { + FruitBananas, + FruitApple, + FruitGrapes, + FruitOrange, + FruitPear, + Droplet } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index 606a935229..ee88edbea1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -3,7 +3,7 @@ using System.Linq; using osu.Framework.Graphics; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index e4ad49ea50..f3b566f340 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects { public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation { - public const double OBJECT_RADIUS = 44; + public const float OBJECT_RADIUS = 64; private float x; @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects public int IndexInBeatmap { get; set; } - public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4); + public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); public virtual bool NewCombo { get; set; } @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Catch.Objects TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); - Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; + Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } protected override HitWindows CreateHitWindows() => HitWindows.Empty; @@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Catch.Objects { Pear, Grape, - Raspberry, Pineapple, + Raspberry, Banana // banananananannaanana } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs deleted file mode 100644 index 5afdb14888..0000000000 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableBanana : DrawableFruit - { - public DrawableBanana(Banana h) - : base(h) - { - } - } -} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs deleted file mode 100644 index 059310d671..0000000000 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ /dev/null @@ -1,33 +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; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using osuTK; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableDroplet : PalpableCatchHitObject - { - private Pulp pulp; - - public override bool StaysOnPlate => false; - - public DrawableDroplet(Droplet h) - : base(h) - { - Origin = Anchor.Centre; - Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4; - Masking = false; - } - - [BackgroundDependencyLoader] - private void load() - { - AddInternal(pulp = new Pulp { Size = Size }); - - AccentColour.BindValueChanged(colour => { pulp.AccentColour = colour.NewValue; }, true); - } - } -} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs deleted file mode 100644 index 53a018c9f4..0000000000 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ /dev/null @@ -1,316 +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 System; -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.Graphics.Shapes; -using osu.Framework.Utils; -using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Objects.Drawable -{ - public class DrawableFruit : PalpableCatchHitObject - { - private Circle border; - - private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust; - - /// - /// Because we're adding a border around the fruit, we need to scale down some. - /// - private const float radius_adjust = 1.1f; - - public DrawableFruit(Fruit h) - : base(h) - { - Origin = Anchor.Centre; - - Size = new Vector2(drawable_radius); - Masking = false; - - Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; - } - - [BackgroundDependencyLoader] - private void load() - { - // todo: this should come from the skin. - AccentColour.Value = colourForRepresentation(HitObject.VisualRepresentation); - - AddRangeInternal(new[] - { - createPulp(HitObject.VisualRepresentation), - border = new Circle - { - EdgeEffect = new EdgeEffectParameters - { - Hollow = !HitObject.HyperDash, - Type = EdgeEffectType.Glow, - Radius = 4 * radius_adjust, - Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Value.Darken(1).Opacity(0.6f) - }, - Size = new Vector2(Height), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BorderColour = Color4.White, - BorderThickness = 3f * radius_adjust, - Children = new Framework.Graphics.Drawable[] - { - new Box - { - AlwaysPresent = true, - Colour = AccentColour.Value, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }, - }); - - if (HitObject.HyperDash) - { - AddInternal(new Pulp - { - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AccentColour = Color4.Red, - Blending = BlendingParameters.Additive, - Alpha = 0.5f, - Scale = new Vector2(1.333f) - }); - } - } - - private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) - { - const float large_pulp_3 = 8f * radius_adjust; - const float distance_from_centre_3 = 0.15f; - - const float large_pulp_4 = large_pulp_3 * 0.925f; - const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; - - const float small_pulp = large_pulp_3 / 2; - - static Vector2 positionAt(float angle, float distance) => new Vector2( - distance * MathF.Sin(angle * MathF.PI / 180), - distance * MathF.Cos(angle * MathF.PI / 180)); - - switch (representation) - { - default: - return new Container(); - - case FruitVisualRepresentation.Raspberry: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(small_pulp), - Y = -0.34f, - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(0, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(90, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(180, distance_from_centre_4), - }, - new Pulp - { - Size = new Vector2(large_pulp_4), - AccentColour = AccentColour.Value, - Position = positionAt(270, distance_from_centre_4), - }, - } - }; - - case FruitVisualRepresentation.Pineapple: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(small_pulp), - Y = -0.3f, - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(45, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(135, distance_from_centre_4), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4), - Position = positionAt(225, distance_from_centre_4), - }, - new Pulp - { - Size = new Vector2(large_pulp_4), - AccentColour = AccentColour.Value, - Position = positionAt(315, distance_from_centre_4), - }, - } - }; - - case FruitVisualRepresentation.Pear: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(small_pulp), - Y = -0.33f, - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_3), - Position = positionAt(60, distance_from_centre_3), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_3), - Position = positionAt(180, distance_from_centre_3), - }, - new Pulp - { - Size = new Vector2(large_pulp_3), - AccentColour = AccentColour.Value, - Position = positionAt(300, distance_from_centre_3), - }, - } - }; - - case FruitVisualRepresentation.Grape: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(small_pulp), - Y = -0.25f, - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_3), - Position = positionAt(0, distance_from_centre_3), - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_3), - Position = positionAt(120, distance_from_centre_3), - }, - new Pulp - { - Size = new Vector2(large_pulp_3), - AccentColour = AccentColour.Value, - Position = positionAt(240, distance_from_centre_3), - }, - } - }; - - case FruitVisualRepresentation.Banana: - return new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Framework.Graphics.Drawable[] - { - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(small_pulp), - Y = -0.3f - }, - new Pulp - { - AccentColour = AccentColour.Value, - Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f), - Y = 0.05f, - }, - } - }; - } - } - - protected override void Update() - { - base.Update(); - - border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); - } - - private Color4 colourForRepresentation(FruitVisualRepresentation representation) - { - switch (representation) - { - default: - case FruitVisualRepresentation.Pear: - return new Color4(17, 136, 170, 255); - - case FruitVisualRepresentation.Grape: - return new Color4(204, 102, 0, 255); - - case FruitVisualRepresentation.Raspberry: - return new Color4(121, 9, 13, 255); - - case FruitVisualRepresentation.Pineapple: - return new Color4(102, 136, 0, 255); - - case FruitVisualRepresentation.Banana: - switch (RNG.Next(0, 3)) - { - default: - return new Color4(255, 240, 0, 255); - - case 1: - return new Color4(255, 192, 0, 255); - - case 2: - return new Color4(214, 221, 28, 255); - } - } - } - } -} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs new file mode 100644 index 0000000000..ebb0bf0f2c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs @@ -0,0 +1,31 @@ +// 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.Rulesets.Catch.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class BananaPiece : PulpFormation + { + public BananaPiece() + { + InternalChildren = new Drawable[] + { + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(SMALL_PULP), + Y = -0.3f + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f), + Y = 0.05f, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs new file mode 100644 index 0000000000..cf7231ebb2 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.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 System.Collections.Generic; +using osu.Framework.Utils; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class DrawableBanana : DrawableFruit + { + public DrawableBanana(Banana h) + : base(h) + { + } + + private Color4? colour; + + protected override Color4 GetComboColour(IReadOnlyList comboColours) + { + // override any external colour changes with banananana + return colour ??= getBananaColour(); + } + + private Color4 getBananaColour() + { + switch (RNG.Next(0, 3)) + { + default: + return new Color4(255, 240, 0, 255); + + case 1: + return new Color4(255, 192, 0, 255); + + case 2: + return new Color4(214, 221, 28, 255); + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs similarity index 97% rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs index ea415e18fa..4ce80aceb8 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Rulesets.Catch.Objects.Drawable +namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableBananaShower : DrawableCatchHitObject { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs similarity index 70% rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index b7c05392f3..5bfe0515a1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -2,24 +2,51 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; +using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Rulesets.Catch.Objects.Drawable +namespace osu.Game.Rulesets.Catch.Objects.Drawables { public abstract class PalpableCatchHitObject : DrawableCatchHitObject where TObject : CatchHitObject { public override bool CanBePlated => true; + protected Container ScaleContainer { get; private set; } + protected PalpableCatchHitObject(TObject hitObject) : base(hitObject) { - Scale = new Vector2(HitObject.Scale); + Origin = Anchor.Centre; + Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); + Masking = false; } + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + ScaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + }); + + ScaleContainer.Scale = new Vector2(HitObject.Scale); + } + + protected override Color4 GetComboColour(IReadOnlyList comboColours) => + comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count]; } public abstract class DrawableCatchHitObject : DrawableCatchHitObject @@ -41,6 +68,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable public virtual bool StaysOnPlate => CanBePlated; + public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; + protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs new file mode 100644 index 0000000000..0a8e830af9 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Utils; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class DrawableDroplet : PalpableCatchHitObject + { + public override bool StaysOnPlate => false; + + public DrawableDroplet(Droplet h) + : base(h) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp + { + Size = Size / 4, + AccentColour = { BindTarget = AccentColour } + }); + } + + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + // roughly matches osu-stable + float startRotation = RNG.NextSingle() * 20; + double duration = HitObject.TimePreempt + 2000; + + this.RotateTo(startRotation).RotateTo(startRotation + 720, duration); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs new file mode 100644 index 0000000000..197ad41247 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs @@ -0,0 +1,50 @@ +// 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.Utils; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class DrawableFruit : PalpableCatchHitObject + { + public DrawableFruit(Fruit h) + : base(h) + { + Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; + } + + [BackgroundDependencyLoader] + private void load() + { + ScaleContainer.Child = new SkinnableDrawable( + new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece()); + } + + private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation) + { + switch (hitObjectVisualRepresentation) + { + case FruitVisualRepresentation.Pear: + return CatchSkinComponents.FruitPear; + + case FruitVisualRepresentation.Grape: + return CatchSkinComponents.FruitGrapes; + + case FruitVisualRepresentation.Pineapple: + return CatchSkinComponents.FruitApple; + + case FruitVisualRepresentation.Raspberry: + return CatchSkinComponents.FruitOrange; + + case FruitVisualRepresentation.Banana: + return CatchSkinComponents.FruitBananas; + + default: + throw new ArgumentOutOfRangeException(nameof(hitObjectVisualRepresentation), hitObjectVisualRepresentation, null); + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs similarity index 87% rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs index a24821b3ce..932464cfd1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Rulesets.Catch.Objects.Drawable +namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableJuiceStream : DrawableCatchHitObject { @@ -42,10 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable switch (hitObject) { case CatchHitObject catchObject: - return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); + return createDrawableRepresentation?.Invoke(catchObject)?.With(o => + ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false); } - return base.CreateNestedHitObject(hitObject); + throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}."); } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs similarity index 60% rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs index d41aea1e7b..ae775684d8 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs @@ -1,16 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Allocation; -namespace osu.Game.Rulesets.Catch.Objects.Drawable +namespace osu.Game.Rulesets.Catch.Objects.Drawables { public class DrawableTinyDroplet : DrawableDroplet { public DrawableTinyDroplet(TinyDroplet h) : base(h) { - Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8; + } + + [BackgroundDependencyLoader] + private void load() + { + ScaleContainer.Scale /= 2; } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs new file mode 100644 index 0000000000..0f5044eda7 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs @@ -0,0 +1,109 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + internal class FruitPiece : CompositeDrawable + { + /// + /// Because we're adding a border around the fruit, we need to scale down some. + /// + public const float RADIUS_ADJUST = 1.1f; + + private Circle border; + + private CatchHitObject hitObject; + + private readonly IBindable accentColour = new Bindable(); + + public FruitPiece() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject) + { + DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; + hitObject = drawableCatchObject.HitObject; + + accentColour.BindTo(drawableCatchObject.AccentColour); + + AddRangeInternal(new[] + { + getFruitFor(drawableCatchObject.HitObject.VisualRepresentation), + border = new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BorderColour = Color4.White, + BorderThickness = 6f * RADIUS_ADJUST, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }, + }); + + if (hitObject.HyperDash) + { + AddInternal(new Pulp + { + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = { Value = Color4.Red }, + Blending = BlendingParameters.Additive, + Alpha = 0.5f, + Scale = new Vector2(1.333f) + }); + } + } + + protected override void Update() + { + base.Update(); + border.Alpha = (float)Math.Clamp((hitObject.StartTime - Time.Current) / 500, 0, 1); + } + + private Drawable getFruitFor(FruitVisualRepresentation representation) + { + switch (representation) + { + case FruitVisualRepresentation.Pear: + return new PearPiece(); + + case FruitVisualRepresentation.Grape: + return new GrapePiece(); + + case FruitVisualRepresentation.Pineapple: + return new PineapplePiece(); + + case FruitVisualRepresentation.Banana: + return new BananaPiece(); + + case FruitVisualRepresentation.Raspberry: + return new RaspberryPiece(); + } + + return Empty(); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs new file mode 100644 index 0000000000..1d1faf893b --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs @@ -0,0 +1,43 @@ +// 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.Rulesets.Catch.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class GrapePiece : PulpFormation + { + public GrapePiece() + { + InternalChildren = new Drawable[] + { + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(SMALL_PULP), + Y = -0.25f, + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_3), + Position = PositionAt(0, DISTANCE_FROM_CENTRE_3), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_3), + Position = PositionAt(120, DISTANCE_FROM_CENTRE_3), + }, + new Pulp + { + Size = new Vector2(LARGE_PULP_3), + AccentColour = { BindTarget = AccentColour }, + Position = PositionAt(240, DISTANCE_FROM_CENTRE_3), + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs new file mode 100644 index 0000000000..7f14217cda --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs @@ -0,0 +1,43 @@ +// 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.Rulesets.Catch.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class PearPiece : PulpFormation + { + public PearPiece() + { + InternalChildren = new Drawable[] + { + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(SMALL_PULP), + Y = -0.33f, + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_3), + Position = PositionAt(60, DISTANCE_FROM_CENTRE_3), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_3), + Position = PositionAt(180, DISTANCE_FROM_CENTRE_3), + }, + new Pulp + { + Size = new Vector2(LARGE_PULP_3), + AccentColour = { BindTarget = AccentColour }, + Position = PositionAt(300, DISTANCE_FROM_CENTRE_3), + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs similarity index 62% rename from osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs index 1e9daf18db..1e7506a257 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs @@ -1,16 +1,16 @@ // 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.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osuTK.Graphics; -namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces +namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces { - public class Pulp : Circle, IHasAccentColour + public class Pulp : Circle { public Pulp() { @@ -22,32 +22,23 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces Colour = Color4.White.Opacity(0.9f); } - private Color4 accentColour; + public readonly Bindable AccentColour = new Bindable(); - public Color4 AccentColour + protected override void LoadComplete() { - get => accentColour; - set - { - accentColour = value; - if (IsLoaded) updateAccentColour(); - } + base.LoadComplete(); + + AccentColour.BindValueChanged(updateAccentColour, true); } - private void updateAccentColour() + private void updateAccentColour(ValueChangedEvent colour) { EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, Radius = Size.X / 2, - Colour = accentColour.Darken(0.2f).Opacity(0.75f) + Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f) }; } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateAccentColour(); - } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs new file mode 100644 index 0000000000..c328ba1837 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.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.Graphics; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class PineapplePiece : PulpFormation + { + public PineapplePiece() + { + InternalChildren = new Drawable[] + { + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(SMALL_PULP), + Y = -0.3f, + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(45, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(135, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(225, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + Size = new Vector2(LARGE_PULP_4), + AccentColour = { BindTarget = AccentColour }, + Position = PositionAt(315, DISTANCE_FROM_CENTRE_4), + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs new file mode 100644 index 0000000000..be70c3400c --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs @@ -0,0 +1,43 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public abstract class PulpFormation : CompositeDrawable + { + protected readonly IBindable AccentColour = new Bindable(); + + protected const float LARGE_PULP_3 = 16f * FruitPiece.RADIUS_ADJUST; + protected const float DISTANCE_FROM_CENTRE_3 = 0.15f; + + protected const float LARGE_PULP_4 = LARGE_PULP_3 * 0.925f; + protected const float DISTANCE_FROM_CENTRE_4 = DISTANCE_FROM_CENTRE_3 / 0.925f; + + protected const float SMALL_PULP = LARGE_PULP_3 / 2; + + protected PulpFormation() + { + RelativeSizeAxes = Axes.Both; + } + + protected static Vector2 PositionAt(float angle, float distance) => new Vector2( + distance * MathF.Sin(angle * MathF.PI / 180), + distance * MathF.Cos(angle * MathF.PI / 180)); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject) + { + DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; + AccentColour.BindTo(drawableCatchObject.AccentColour); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs new file mode 100644 index 0000000000..22ce3ba5b3 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.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.Graphics; +using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Objects.Drawables +{ + public class RaspberryPiece : PulpFormation + { + public RaspberryPiece() + { + InternalChildren = new Drawable[] + { + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(SMALL_PULP), + Y = -0.34f, + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(0, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(90, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + AccentColour = { BindTarget = AccentColour }, + Size = new Vector2(LARGE_PULP_4), + Position = PositionAt(180, DISTANCE_FROM_CENTRE_4), + }, + new Pulp + { + Size = new Vector2(LARGE_PULP_4), + AccentColour = { BindTarget = AccentColour }, + Position = PositionAt(270, DISTANCE_FROM_CENTRE_4), + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs new file mode 100644 index 0000000000..36164c5543 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + public class CatchLegacySkinTransformer : ISkin + { + private readonly ISkin source; + + public CatchLegacySkinTransformer(ISkinSource source) + { + this.source = source; + } + + public Drawable GetDrawableComponent(ISkinComponent component) + { + if (!(component is CatchSkinComponent catchSkinComponent)) + return null; + + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.FruitApple: + case CatchSkinComponents.FruitBananas: + case CatchSkinComponents.FruitOrange: + case CatchSkinComponents.FruitGrapes: + case CatchSkinComponents.FruitPear: + var lookupName = catchSkinComponent.Component.ToString().Kebaberize(); + if (GetTexture(lookupName) != null) + return new LegacyFruitPiece(lookupName); + + break; + + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyFruitPiece("fruit-drop") { Scale = new Vector2(0.8f) }; + + break; + } + + return null; + } + + public Texture GetTexture(string componentName) => source.GetTexture(componentName); + + public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); + + public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup); + } +} diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs new file mode 100644 index 0000000000..2631fe5487 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + internal class LegacyFruitPiece : CompositeDrawable + { + private readonly string lookupName; + + private readonly IBindable accentColour = new Bindable(); + private Sprite colouredSprite; + + public LegacyFruitPiece(string lookupName) + { + this.lookupName = lookupName; + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject; + + accentColour.BindTo(drawableCatchObject.AccentColour); + + InternalChildren = new Drawable[] + { + colouredSprite = new Sprite + { + Texture = skin.GetTexture(lookupName), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Sprite + { + Texture = skin.GetTexture($"{lookupName}-overlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2d71fb93fb..2319c5ac1f 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 1ad12dc4ad..fae928e168 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -11,7 +11,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -74,11 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI caughtFruit.Anchor = Anchor.TopCentre; caughtFruit.Origin = Anchor.Centre; - caughtFruit.Scale *= 0.7f; + caughtFruit.Scale *= 0.5f; caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime; caughtFruit.LifetimeEnd = double.MaxValue; - MovableCatcher.Add(caughtFruit); + MovableCatcher.PlaceOnPlate(caughtFruit); lastPlateableFruit = caughtFruit; if (!fruit.StaysOnPlate) @@ -221,9 +221,9 @@ namespace osu.Game.Rulesets.Catch.UI /// Add a caught fruit to the catcher's stack. /// /// The fruit that was caught. - public void Add(DrawableHitObject fruit) + public void PlaceOnPlate(DrawableCatchHitObject fruit) { - float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X; + float ourRadius = fruit.DisplayRadius; float theirRadius = 0; const float allowance = 6; diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index fdd820b891..fd8a1d175d 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -8,7 +8,7 @@ using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.Objects.Drawable; +using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs deleted file mode 100644 index 80b1b3df8e..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs +++ /dev/null @@ -1,46 +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 System; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; -using osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Mania.Tests -{ - public abstract class SkinnableTestScene : OsuGridTestScene - { - private Skin defaultSkin; - - protected SkinnableTestScene() - : base(1, 2) - { - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio, SkinManager skinManager) - { - defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); - } - - public void SetContents(Func creationFunction) - { - Cell(0).Child = createProvider(null, creationFunction); - Cell(1).Child = createProvider(defaultSkin, creationFunction); - } - - private Drawable createProvider(Skin skin, Func creationFunction) - { - var mainProvider = new SkinProvidingContainer(skin); - - return mainProvider - .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) - { - Child = creationFunction() - }); - } - } -} diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs index eea1a36a19..692d079c16 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Game.Tests.Visual; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 4f54e451b7..9fbe8f7ffe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer typeof(DrawableRoomPlaylistItem) }; - private DrawableRoomPlaylist playlist; + private TestPlaylist playlist; [Test] public void TestNonEditableNonSelectable() @@ -211,30 +211,45 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertDeleteButtonVisibility(int index, bool visible) => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); - private void createPlaylist(bool allowEdit, bool allowSelection) => AddStep("create playlist", () => + private void createPlaylist(bool allowEdit, bool allowSelection) { - Child = playlist = new DrawableRoomPlaylist(allowEdit, allowSelection) + AddStep("create playlist", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300) - }; - - for (int i = 0; i < 20; i++) - { - playlist.Items.Add(new PlaylistItem + Child = playlist = new TestPlaylist(allowEdit, allowSelection) { - ID = i, - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo }, - RequiredMods = + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + for (int i = 0; i < 20; i++) + { + playlist.Items.Add(new PlaylistItem { - new OsuModHardRock(), - new OsuModDoubleTime(), - new OsuModAutoplay() - } - }); + ID = i, + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + + private class TestPlaylist : DrawableRoomPlaylist + { + public new IReadOnlyDictionary> ItemMap => base.ItemMap; + + public TestPlaylist(bool allowEdit, bool allowSelection) + : base(allowEdit, allowSelection) + { } - }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs new file mode 100644 index 0000000000..1e1bc9725c --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.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.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.RoomStatuses; +using osu.Game.Screens.Multi.Lounge.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneLoungeRoomInfo : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RoomInfo) + }; + + [SetUp] + public void Setup() => Schedule(() => + { + Room.CopyFrom(new Room()); + + Child = new RoomInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500 + }; + }); + + public override void SetUpSteps() + { + // Todo: Temp + } + + [Test] + public void TestNonSelectedRoom() + { + AddStep("set null room", () => Room.RoomID.Value = null); + } + + [Test] + public void TestOpenRoom() + { + AddStep("set open room", () => + { + Room.RoomID.Value = 0; + Room.Name.Value = "Room 0"; + Room.Host.Value = new User { Username = "peppy", Id = 2 }; + Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1); + Room.Status.Value = new RoomStatusOpen(); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs index e07ebc1454..1fc258a225 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs @@ -1,27 +1,57 @@ // 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Multi.Components; using osuTK; namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneOverlinedParticipants : MultiplayerTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OverlinedParticipants), + typeof(OverlinedDisplay), + typeof(ParticipantsList) + }; + protected override bool UseOnlineAPI => true; public TestSceneOverlinedParticipants() { Room.RoomID.Value = 7; + } - Add(new Container + [Test] + public void TestHorizontalLayout() + { + AddStep("create component", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - Child = new OverlinedParticipants() + Child = new OverlinedParticipants(Direction.Horizontal) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 500, + AutoSizeAxes = Axes.Y, + }; + }); + } + + [Test] + public void TestVerticalLayout() + { + AddStep("create component", () => + { + Child = new OverlinedParticipants(Direction.Vertical) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500) + }; }); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs index cf4897be50..14b7934dc7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs @@ -2,10 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Multi.Components; using osu.Game.Tests.Beatmaps; using osuTK; @@ -27,12 +26,11 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - Add(new Container + Add(new OverlinedPlaylist(false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500), - Child = new OverlinedPlaylist(false) }); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs new file mode 100644 index 0000000000..7c05d99c59 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -0,0 +1,39 @@ +// 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 osu.Game.Overlays; +using NUnit.Framework; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapListingOverlay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatmapListingOverlay), + }; + + protected override bool UseOnlineAPI => true; + + private readonly BeatmapListingOverlay overlay; + + public TestSceneBeatmapListingOverlay() + { + Add(overlay = new BeatmapListingOverlay()); + } + + [Test] + public void TestShow() + { + AddStep("Show", overlay.Show); + } + + [Test] + public void TestHide() + { + AddStep("Hide", overlay.Hide); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index a769ebe4a9..83e5cd0fe7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -29,8 +29,8 @@ namespace osu.Game.Tests.Visual.Online typeof(RankingsOverlayHeader) }; - [Cached] - private RankingsOverlay rankingsOverlay; + [Cached(typeof(RankingsOverlay))] + private readonly RankingsOverlay rankingsOverlay; private readonly Bindable countryBindable = new Bindable(); private readonly Bindable scope = new Bindable(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 3c959e05c1..51f4089058 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -195,6 +195,29 @@ namespace osu.Game.Tests.Visual.Online Position = 1337, }; + var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo + { + Score = new APILegacyScoreInfo + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + Position = null, + }; + var oneScore = new APILegacyScores { Scores = new List @@ -250,6 +273,12 @@ namespace osu.Game.Tests.Visual.Online allScores.UserScore = myBestScore; scoresContainer.Scores = allScores; }); + + AddStep("Load scores with null my best position", () => + { + allScores.UserScore = myBestScoreWithNullPosition; + scoresContainer.Scores = allScores; + }); } private class TestScoresContainer : ScoresContainer diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 3eff75b020..1198488bda 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -59,6 +59,33 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep(@"None selected", () => leaderboard.SetRetrievalState(PlaceholderState.NoneSelected)); foreach (BeatmapSetOnlineStatus status in Enum.GetValues(typeof(BeatmapSetOnlineStatus))) AddStep($"{status} beatmap", () => showBeatmapWithStatus(status)); + AddStep("null personal best position", showPersonalBestWithNullPosition); + } + + private void showPersonalBestWithNullPosition() + { + leaderboard.TopScore = new APILegacyUserTopScoreInfo + { + Position = null, + Score = new APILegacyScoreInfo + { + Rank = ScoreRank.XH, + Accuracy = 1, + MaxCombo = 244, + TotalScore = 1707827, + Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, }, + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + } + }; } private void showPersonalBest() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 9474c08c5a..51d302123b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -502,6 +502,72 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID); } + [Test] + public void TestDifficultyIconSelecting() + { + addRulesetImportStep(0); + createSongSelect(); + + DrawableCarouselBeatmapSet set = null; + AddStep("Find the DrawableCarouselBeatmapSet", () => + { + set = songSelect.Carousel.ChildrenOfType().First(); + }); + + DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null; + AddStep("Find an icon", () => + { + difficultyIcon = set.ChildrenOfType() + .First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); + }); + AddStep("Click on a difficulty", () => + { + InputManager.MoveMouseTo(difficultyIcon); + + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon)); + + double? maxBPM = null; + AddStep("Filter some difficulties", () => songSelect.Carousel.Filter(new FilterCriteria + { + BPM = new FilterCriteria.OptionalRange + { + Min = maxBPM = songSelect.Carousel.SelectedBeatmapSet.MaxBPM, + IsLowerInclusive = true + } + })); + + DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null; + AddStep("Get filtered icon", () => + { + var filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.Find(b => b.BPM < maxBPM); + int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap); + filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); + }); + + int? previousID = null; + AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID); + AddStep("Click on a filtered difficulty", () => + { + InputManager.MoveMouseTo(filteredIcon); + + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID); + } + + private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info); + + private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap); + + private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, DrawableCarouselBeatmapSet.FilterableDifficultyIcon icon) + { + return set.ChildrenOfType().ToList().FindIndex(i => i == icon); + } + private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs new file mode 100644 index 0000000000..1d8db71527 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchSection.cs @@ -0,0 +1,90 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneBeatmapListingSearchSection : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatmapListingSearchSection), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + private readonly BeatmapListingSearchSection section; + + public TestSceneBeatmapListingSearchSection() + { + OsuSpriteText query; + OsuSpriteText ruleset; + OsuSpriteText category; + + Add(section = new BeatmapListingSearchSection + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + query = new OsuSpriteText(), + ruleset = new OsuSpriteText(), + category = new OsuSpriteText(), + } + }); + + section.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); + section.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); + section.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); + } + + [Test] + public void TestCovers() + { + AddStep("Set beatmap", () => section.BeatmapSet = beatmap_set); + AddStep("Set beatmap (no cover)", () => section.BeatmapSet = no_cover_beatmap_set); + AddStep("Set null beatmap", () => section.BeatmapSet = null); + } + + private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305" + } + } + }; + + private static readonly BeatmapSetInfo no_cover_beatmap_set = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = string.Empty + } + } + }; + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs new file mode 100644 index 0000000000..7b0b644dab --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentEditor.cs @@ -0,0 +1,157 @@ +// 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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Comments; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneCommentEditor : ManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CommentEditor), + typeof(CancellableCommentEditor), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + private TestCommentEditor commentEditor; + private TestCancellableCommentEditor cancellableCommentEditor; + + [SetUp] + public void SetUp() => Schedule(() => + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 800, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + commentEditor = new TestCommentEditor(), + cancellableCommentEditor = new TestCancellableCommentEditor() + } + })); + + [Test] + public void TestCommitViaKeyboard() + { + AddStep("click on text box", () => + { + InputManager.MoveMouseTo(commentEditor); + InputManager.Click(MouseButton.Left); + }); + AddStep("enter text", () => commentEditor.Current.Value = "text"); + + AddStep("press Enter", () => press(Key.Enter)); + + AddAssert("text committed", () => commentEditor.CommittedText == "text"); + AddAssert("button is loading", () => commentEditor.IsLoading); + } + + [Test] + public void TestCommitViaKeyboardWhenEmpty() + { + AddStep("click on text box", () => + { + InputManager.MoveMouseTo(commentEditor); + InputManager.Click(MouseButton.Left); + }); + + AddStep("press Enter", () => press(Key.Enter)); + + AddAssert("no text committed", () => commentEditor.CommittedText == null); + AddAssert("button is not loading", () => !commentEditor.IsLoading); + } + + [Test] + public void TestCommitViaButton() + { + AddStep("click on text box", () => + { + InputManager.MoveMouseTo(commentEditor); + InputManager.Click(MouseButton.Left); + }); + AddStep("enter text", () => commentEditor.Current.Value = "some other text"); + + AddStep("click submit", () => + { + InputManager.MoveMouseTo(commentEditor.ButtonsContainer); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("text committed", () => commentEditor.CommittedText == "some other text"); + AddAssert("button is loading", () => commentEditor.IsLoading); + } + + [Test] + public void TestCancelAction() + { + AddStep("click cancel button", () => + { + InputManager.MoveMouseTo(cancellableCommentEditor.ButtonsContainer); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("cancel action fired", () => cancellableCommentEditor.Cancelled); + } + + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + + private class TestCommentEditor : CommentEditor + { + public new Bindable Current => base.Current; + public new FillFlowContainer ButtonsContainer => base.ButtonsContainer; + + public string CommittedText { get; private set; } + + public TestCommentEditor() + { + OnCommit = onCommit; + } + + private void onCommit(string value) + { + CommittedText = value; + Scheduler.AddDelayed(() => IsLoading = false, 1000); + } + + protected override string FooterText => @"Footer text. And it is pretty long. Cool."; + protected override string CommitButtonText => @"Commit"; + protected override string TextBoxPlaceholder => @"This text box is empty"; + } + + private class TestCancellableCommentEditor : CancellableCommentEditor + { + public new FillFlowContainer ButtonsContainer => base.ButtonsContainer; + protected override string FooterText => @"Wow, another one. Sicc"; + + public bool Cancelled { get; private set; } + + public TestCancellableCommentEditor() + { + OnCancel = () => Cancelled = true; + } + + protected override string CommitButtonText => @"Save"; + protected override string TextBoxPlaceholder => @"Multiline textboxes soon"; + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneProcessingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProcessingOverlay.cs new file mode 100644 index 0000000000..2424078e5a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProcessingOverlay.cs @@ -0,0 +1,86 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneProcessingOverlay : OsuTestScene + { + private Drawable dimContent; + private ProcessingOverlay overlay; + + [SetUp] + public void SetUp() => Schedule(() => + { + Children = new[] + { + new Container + { + Size = new Vector2(300), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new[] + { + new Box + { + Colour = Color4.SlateGray, + RelativeSizeAxes = Axes.Both, + }, + dimContent = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.9f), + Children = new Drawable[] + { + new OsuSpriteText { Text = "Sample content" }, + new TriangleButton { Text = "can't puush me", Width = 200, }, + new TriangleButton { Text = "puush me", Width = 200, Action = () => { } }, + } + }, + overlay = new ProcessingOverlay(dimContent), + } + }, + }; + }); + + [Test] + public void ShowHide() + { + AddAssert("not visible", () => !overlay.IsPresent); + + AddStep("show", () => overlay.Show()); + + AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White); + + AddStep("hide", () => overlay.Hide()); + + AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White); + } + + [Test] + public void ContentRestoreOnDispose() + { + AddAssert("not visible", () => !overlay.IsPresent); + + AddStep("show", () => overlay.Show()); + + AddUntilStep("wait for content dim", () => dimContent.Colour != Color4.White); + + AddStep("hide", () => overlay.Expire()); + + AddUntilStep("wait for content restore", () => dimContent.Colour == Color4.White); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index bcc9ab885e..68d113ce40 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -51,6 +51,9 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapOnlineInfo OnlineInfo { get; set; } + [NotMapped] + public int? MaxCombo { get; set; } + /// /// The playable length in milliseconds of this beatmap. /// diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 16eecb7198..c60bd0286e 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -50,7 +49,7 @@ namespace osu.Game.Beatmaps.Drawables Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.1f)), + Colour = OsuColour.Gray(0.2f), }; } diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 6d531887ed..1824fcd878 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -14,6 +14,9 @@ namespace osu.Game.Graphics.Containers { public class OsuScrollContainer : ScrollContainer { + public const float SCROLL_BAR_HEIGHT = 10; + public const float SCROLL_BAR_PADDING = 3; + /// /// Allows controlling the scroll bar from any position in the container using the right mouse button. /// Uses the value of to smoothly scroll to the dragged location. @@ -96,8 +99,6 @@ namespace osu.Game.Graphics.Containers protected class OsuScrollbar : ScrollbarContainer { - private const float dim_size = 10; - private Color4 hoverColour; private Color4 defaultColour; private Color4 highlightColour; @@ -135,7 +136,7 @@ namespace osu.Game.Graphics.Containers public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None) { - Vector2 size = new Vector2(dim_size) + Vector2 size = new Vector2(SCROLL_BAR_HEIGHT) { [(int)ScrollDirection] = val }; diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 0224c77ee8..8c520f4e10 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -4,13 +4,16 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Utils; +using osuTK; namespace osu.Game.Graphics { - public class DrawableDate : OsuSpriteText, IHasTooltip + public class DrawableDate : OsuSpriteText, IHasCustomTooltip { private DateTimeOffset date; @@ -75,6 +78,72 @@ namespace osu.Game.Graphics private void updateTime() => Text = Format(); - public virtual string TooltipText => string.Format($"{Date:MMMM d, yyyy h:mm tt \"UTC\"z}"); + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => Date; + + private class DateTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText dateText, timeText; + private readonly Box background; + + public DateTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + dateText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + timeText.Colour = colours.BlueLighter; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public bool SetContent(object content) + { + if (!(content is DateTimeOffset date)) + return false; + + dateText.Text = $"{date:d MMMM yyyy} "; + timeText.Text = $"{date:hh:mm:ss \"UTC\"z}"; + return true; + } + + public void Move(Vector2 pos) => Position = pos; + } } } diff --git a/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs index d75e78e2d9..c65801a82e 100644 --- a/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs +++ b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs @@ -6,20 +6,27 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { /// - /// An overlay that will consume all available space and block input when required. + /// An overlay that will show a loading overlay and completely block input to an area. + /// Also optionally dims target elements. /// Useful for disabling all elements in a form and showing we are waiting on a response, for instance. /// public class ProcessingOverlay : VisibilityContainer { - private const float transition_duration = 200; + private readonly Drawable dimTarget; - public ProcessingOverlay() + private Container loadingBox; + + private const float transition_duration = 600; + + public ProcessingOverlay(Drawable dimTarget = null) { + this.dimTarget = dimTarget; RelativeSizeAxes = Axes.Both; } @@ -28,29 +35,54 @@ namespace osu.Game.Graphics.UserInterface { InternalChildren = new Drawable[] { - new Box + loadingBox = new Container { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both, - Alpha = 0.9f, + Size = new Vector2(80), + Scale = new Vector2(0.8f), + Masking = true, + CornerRadius = 15, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new LoadingAnimation { State = { Value = Visibility.Visible } } + } }, - new LoadingAnimation { State = { Value = Visibility.Visible } } }; } - protected override bool Handle(UIEvent e) - { - return true; - } + protected override bool Handle(UIEvent e) => true; protected override void PopIn() { - this.FadeIn(transition_duration * 2, Easing.OutQuint); + this.FadeIn(transition_duration, Easing.OutQuint); + loadingBox.ScaleTo(1, transition_duration, Easing.OutElastic); + + dimTarget?.FadeColour(OsuColour.Gray(0.5f), transition_duration, Easing.OutQuint); } protected override void PopOut() { this.FadeOut(transition_duration, Easing.OutQuint); + loadingBox.ScaleTo(0.8f, transition_duration / 2, Easing.In); + + dimTarget?.FadeColour(Color4.White, transition_duration, Easing.OutQuint); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (State.Value == Visibility.Visible) + { + // ensure we don't leave the target in a bad state. + dimTarget?.FadeColour(Color4.White, transition_duration, Easing.OutQuint); + } } } } diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index fe8756a4d2..fe92054d25 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -17,18 +17,16 @@ namespace osu.Game.Graphics.UserInterface public SearchTextBox() { Height = 35; - AddRange(new Drawable[] + Add(new SpriteIcon { - new SpriteIcon - { - Icon = FontAwesome.Solid.Search, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 }, - Size = new Vector2(20), - } + Icon = FontAwesome.Solid.Search, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, + Margin = new MarginPadding { Right = 10 }, + Size = new Vector2(20), }); + TextFlow.Padding = new MarginPadding { Right = 35 }; PlaceholderText = "type to search"; } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index f4d67a56aa..e023a2502f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -61,6 +61,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"failtimes")] private BeatmapMetrics metrics { get; set; } + [JsonProperty(@"max_combo")] + private int? maxCombo { get; set; } + public BeatmapInfo ToBeatmap(RulesetStore rulesets) { var set = BeatmapSet?.ToBeatmapSet(rulesets); @@ -76,6 +79,7 @@ namespace osu.Game.Online.API.Requests.Responses Status = Status, BeatmapSet = set, Metrics = metrics, + MaxCombo = maxCombo, BaseDifficulty = new BeatmapDifficulty { DrainRate = drainRate, diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs index 318fcb00de..75be9171b0 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -18,7 +18,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APILegacyUserTopScoreInfo { [JsonProperty(@"position")] - public int Position; + public int? Position; [JsonProperty(@"score")] public APILegacyScoreInfo Score; diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 5652b8d2bd..930ca8fdf1 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.IO.Network; using osu.Game.Overlays; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; @@ -26,8 +27,21 @@ namespace osu.Game.Online.API.Requests this.direction = direction; } - // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={searchCategory.ToString().ToLowerInvariant()}&sort={sortCriteria.ToString().ToLowerInvariant()}_{directionString}"; + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.AddParameter("q", query); + + if (ruleset.ID.HasValue) + req.AddParameter("m", ruleset.ID.Value.ToString()); + + req.AddParameter("s", searchCategory.ToString().ToLowerInvariant()); + req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}"); + + return req; + } + + protected override string Target => @"beatmapsets/search"; } public enum BeatmapSearchCategory diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs index 28863cb0e0..3c4fb11ed1 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs @@ -2,12 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Newtonsoft.Json; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { public class SearchBeatmapSetsResponse : ResponseWithCursor { + [JsonProperty("beatmapsets")] public IEnumerable BeatmapSets; + + [JsonProperty("total")] + public int Total; } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 1f52a4481b..ba92b993a2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Online.Leaderboards protected Container RankContainer { get; private set; } private readonly ScoreInfo score; - private readonly int rank; + private readonly int? rank; private readonly bool allowHighlight; private Box background; @@ -58,7 +58,7 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } - public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true) + public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { this.score = score; this.rank = rank; @@ -90,7 +90,7 @@ namespace osu.Game.Online.Leaderboards Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 20, italics: true), - Text = rank.ToMetric(decimals: rank < 100000 ? 1 : 0), + Text = rank == null ? "-" : rank.Value.ToMetric(decimals: rank < 100000 ? 1 : 0), }, }, }, diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index be3a84ca00..454fce0261 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -48,9 +48,11 @@ namespace osu.Game.Overlays.AccountCreation [BackgroundDependencyLoader] private void load(OsuColour colours) { + FillFlowContainer mainContent; + InternalChildren = new Drawable[] { - new FillFlowContainer + mainContent = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, @@ -122,7 +124,7 @@ namespace osu.Game.Overlays.AccountCreation }, }, }, - processingOverlay = new ProcessingOverlay { Alpha = 0 } + processingOverlay = new ProcessingOverlay(mainContent) }; textboxes = new[] { usernameTextBox, emailTextBox, passwordTextBox }; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs new file mode 100644 index 0000000000..5af92914de --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.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.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapListingHeader : OverlayHeader + { + protected override ScreenTitle CreateTitle() => new BeatmapListingTitle(); + + private class BeatmapListingTitle : ScreenTitle + { + public BeatmapListingTitle() + { + Title = @"beatmap"; + Section = @"listing"; + } + + protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog"); + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs new file mode 100644 index 0000000000..f9799d8a6b --- /dev/null +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchSection.cs @@ -0,0 +1,127 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osuTK; +using osu.Framework.Bindables; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapListing +{ + public class BeatmapListingSearchSection : CompositeDrawable + { + public Bindable Query => textBox.Current; + + public Bindable Ruleset => modeFilter.Current; + + public Bindable Category => categoryFilter.Current; + + public BeatmapSetInfo BeatmapSet + { + set + { + if (value == null || string.IsNullOrEmpty(value.OnlineInfo.Covers.Cover)) + { + beatmapCover.FadeOut(600, Easing.OutQuint); + return; + } + + beatmapCover.BeatmapSet = value; + beatmapCover.FadeTo(0.1f, 200, Easing.OutQuint); + } + } + + private readonly BeatmapSearchTextBox textBox; + private readonly BeatmapSearchRulesetFilterRow modeFilter; + private readonly BeatmapSearchFilterRow categoryFilter; + + private readonly Box background; + private readonly UpdateableBeatmapSetCover beatmapCover; + + public BeatmapListingSearchSection() + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + AddRangeInternal(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = beatmapCover = new UpdateableBeatmapSetCover + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + } + }, + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding + { + Vertical = 20, + Horizontal = 40, + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + textBox = new BeatmapSearchTextBox + { + RelativeSizeAxes = Axes.X, + }, + new ReverseChildIDFillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Children = new Drawable[] + { + modeFilter = new BeatmapSearchRulesetFilterRow(), + categoryFilter = new BeatmapSearchFilterRow(@"Categories"), + } + } + } + } + } + }); + + Category.Value = BeatmapSearchCategory.Leaderboard; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Dark6; + } + + private class BeatmapSearchTextBox : SearchTextBox + { + protected override Color4 SelectionColour => Color4.Gray; + + public BeatmapSearchTextBox() + { + PlaceholderText = @"type in keywords..."; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs index cb41b33bc4..27c43b092a 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs @@ -8,16 +8,17 @@ using osu.Framework.Graphics; using osuTK.Graphics; using osuTK; using osu.Framework.Input.Events; +using osu.Game.Overlays.Direct; namespace osu.Game.Overlays.BeatmapListing { - public class BeatmapListingSortTabControl : OverlaySortTabControl + public class BeatmapListingSortTabControl : OverlaySortTabControl { public readonly Bindable SortDirection = new Bindable(Overlays.SortDirection.Descending); public BeatmapListingSortTabControl() { - Current.Value = BeatmapSortCriteria.Ranked; + Current.Value = DirectSortCriteria.Ranked; } protected override SortTabControl CreateControl() => new BeatmapSortTabControl @@ -29,7 +30,7 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly Bindable SortDirection = new Bindable(); - protected override TabItem CreateTabItem(BeatmapSortCriteria value) => new BeatmapSortTabItem(value) + protected override TabItem CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value) { SortDirection = { BindTarget = SortDirection } }; @@ -39,12 +40,12 @@ namespace osu.Game.Overlays.BeatmapListing { public readonly Bindable SortDirection = new Bindable(); - public BeatmapSortTabItem(BeatmapSortCriteria value) + public BeatmapSortTabItem(DirectSortCriteria value) : base(value) { } - protected override TabButton CreateTabButton(BeatmapSortCriteria value) => new BeatmapTabButton(value) + protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value) { Active = { BindTarget = Active }, SortDirection = { BindTarget = SortDirection } @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapListing private readonly SpriteIcon icon; - public BeatmapTabButton(BeatmapSortCriteria value) + public BeatmapTabButton(DirectSortCriteria value) : base(value) { Add(icon = new SpriteIcon @@ -104,15 +105,4 @@ namespace osu.Game.Overlays.BeatmapListing } } } - - public enum BeatmapSortCriteria - { - Title, - Artist, - Difficulty, - Ranked, - Rating, - Plays, - Favourites, - } } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs new file mode 100644 index 0000000000..213e9a4244 --- /dev/null +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -0,0 +1,299 @@ +// 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 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.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Threading; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.BeatmapListing; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class BeatmapListingOverlay : FullscreenOverlay + { + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + private SearchBeatmapSetsRequest getSetsRequest; + + private Container panelsPlaceholder; + private Drawable currentContent; + private BeatmapListingSearchSection searchSection; + private BeatmapListingSortTabControl sortControl; + + public BeatmapListingOverlay() + : base(OverlayColourScheme.Blue) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background6 + }, + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new ReverseChildIDFillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 3, + Offset = new Vector2(0f, 1f), + }, + Children = new Drawable[] + { + new BeatmapListingHeader(), + searchSection = new BeatmapListingSearchSection(), + } + }, + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background4, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5 + }, + sortControl = new BeatmapListingSortTabControl + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 20 } + } + } + }, + panelsPlaceholder = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 20 }, + } + } + } + } + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var sortCriteria = sortControl.Current; + var sortDirection = sortControl.SortDirection; + + searchSection.Query.BindValueChanged(query => + { + sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? DirectSortCriteria.Ranked : DirectSortCriteria.Relevance; + sortDirection.Value = SortDirection.Descending; + + queueUpdateSearch(true); + }); + + searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch()); + searchSection.Category.BindValueChanged(_ => queueUpdateSearch()); + sortCriteria.BindValueChanged(_ => queueUpdateSearch()); + sortDirection.BindValueChanged(_ => queueUpdateSearch()); + } + + private ScheduledDelegate queryChangedDebounce; + + private void queueUpdateSearch(bool queryTextChanged = false) + { + getSetsRequest?.Cancel(); + + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + + private void updateSearch() + { + if (!IsLoaded) + return; + + if (State.Value == Visibility.Hidden) + return; + + if (API == null) + return; + + previewTrackManager.StopAnyPlaying(this); + + currentContent?.FadeColour(Color4.DimGray, 400, Easing.OutQuint); + + getSetsRequest = new SearchBeatmapSetsRequest( + searchSection.Query.Value, + searchSection.Ruleset.Value, + searchSection.Category.Value, + sortControl.Current.Value, + sortControl.SortDirection.Value); + + getSetsRequest.Success += response => Schedule(() => recreatePanels(response)); + + API.Queue(getSetsRequest); + } + + private void recreatePanels(SearchBeatmapSetsResponse response) + { + if (response.Total == 0) + { + searchSection.BeatmapSet = null; + LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder); + return; + } + + var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList(); + + var newPanels = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Alpha = 0, + Margin = new MarginPadding { Vertical = 15 }, + ChildrenEnumerable = beatmaps.Select(b => new DirectGridPanel(b) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }) + }; + + LoadComponentAsync(newPanels, loaded => + { + addContentToPlaceholder(loaded); + searchSection.BeatmapSet = beatmaps.First(); + }); + } + + private void addContentToPlaceholder(Drawable content) + { + Drawable lastContent = currentContent; + + if (lastContent != null) + { + lastContent.FadeOut(100, Easing.OutQuint).Expire(); + + // Consider the case when the new content is smaller than the last content. + // If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird. + // At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0. + // To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so. + lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y); + } + + panelsPlaceholder.Add(currentContent = content); + currentContent.FadeIn(200, Easing.OutQuint); + } + + protected override void Dispose(bool isDisposing) + { + getSetsRequest?.Cancel(); + queryChangedDebounce?.Cancel(); + + base.Dispose(isDisposing); + } + + private class NotFoundDrawable : CompositeDrawable + { + public NotFoundDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/not-found") + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"... nope, nothing found.", + } + } + }); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 096e91b65b..446a075ae4 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.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 osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet fields.Children = new Drawable[] { new Field("mapped by", BeatmapSet.Metadata.Author.Username, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), - new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.Bold)) + new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) { Margin = new MarginPadding { Top = 5 }, }, @@ -58,11 +59,11 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - fields.Add(new Field("ranked on", online.Ranked.Value.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field("ranked", online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { - fields.Add(new Field("last updated on", online.LastUpdated.Value.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field("last updated", online.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } } @@ -76,7 +77,7 @@ namespace osu.Game.Overlays.BeatmapSet new Container { AutoSizeAxes = Axes.Both, - CornerRadius = 3, + CornerRadius = 4, Masking = true, Child = avatar = new UpdateableAvatar { @@ -87,7 +88,7 @@ namespace osu.Game.Overlays.BeatmapSet { Colour = Color4.Black.Opacity(0.25f), Type = EdgeEffectType.Shadow, - Radius = 3, + Radius = 4, Offset = new Vector2(0f, 1f), }, }, @@ -117,15 +118,34 @@ namespace osu.Game.Overlays.BeatmapSet new OsuSpriteText { Text = $"{first} ", - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 11) }, new OsuSpriteText { Text = second, - Font = secondFont.With(size: 13) + Font = secondFont.With(size: 11) }, }; } + + public Field(string first, DateTimeOffset second, FontUsage secondFont) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + + Children = new[] + { + new OsuSpriteText + { + Text = $"{first} ", + Font = OsuFont.GetFont(size: 13) + }, + new DrawableDate(second) + { + Font = secondFont.With(size: 13) + } + }; + } } } } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 7092b860a0..ba0a62ec2f 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet { TooltipText = name; RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; + Height = 24f; Children = new Drawable[] { @@ -113,7 +113,8 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, Children = new Drawable[] { new SpriteIcon @@ -121,7 +122,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, Icon = FontAwesome.Solid.Square, - Size = new Vector2(13), + Size = new Vector2(12), Rotation = 45, Colour = OsuColour.FromHex(@"441288"), }, @@ -130,7 +131,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, Icon = icon, - Size = new Vector2(13), + Size = new Vector2(12), Colour = OsuColour.FromHex(@"f7dd55"), Scale = new Vector2(0.8f), }, @@ -139,7 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Left = 10 }, - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), }, }, }, diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index bf2a92cd4f..66886b0882 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -19,7 +18,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -34,7 +32,6 @@ namespace osu.Game.Overlays.BeatmapSet public readonly DifficultiesContainer Difficulties; public readonly Bindable Beatmap = new Bindable(); - private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet @@ -67,7 +64,7 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2) }, + Margin = new MarginPadding { Left = -(tile_icon_padding + tile_spacing / 2), Bottom = 10 }, OnLostHover = () => { showBeatmap(Beatmap.Value); @@ -77,7 +74,6 @@ namespace osu.Game.Overlays.BeatmapSet new FillFlowContainer { AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 10 }, Spacing = new Vector2(5f), Children = new[] { @@ -85,13 +81,13 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold) }, starRating = new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold), Text = "Star Difficulty", Alpha = 0, Margin = new MarginPadding { Bottom = 1 }, @@ -192,9 +188,11 @@ namespace osu.Game.Overlays.BeatmapSet public class DifficultySelectorButton : OsuClickableContainer, IStateful { private const float transition_duration = 100; - private const float size = 52; + private const float size = 54; + private const float background_size = size - 2; - private readonly Container bg; + private readonly Container background; + private readonly Box backgroundBox; private readonly DifficultyIcon icon; public readonly BeatmapInfo Beatmap; @@ -230,16 +228,16 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { - bg = new Container + background = new Container { - RelativeSizeAxes = Axes.Both, + Size = new Vector2(background_size), Masking = true, CornerRadius = 4, - Child = new Box + Child = backgroundBox = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f), - }, + Alpha = 0.5f + } }, icon = new DifficultyIcon(beatmap, shouldShowTooltip: false) { @@ -273,15 +271,21 @@ namespace osu.Game.Overlays.BeatmapSet private void fadeIn() { - bg.FadeIn(transition_duration); + background.FadeIn(transition_duration); icon.FadeIn(transition_duration); } private void fadeOut() { - bg.FadeOut(); + background.FadeOut(); icon.FadeTo(0.7f, transition_duration); } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + backgroundBox.Colour = colourProvider.Background6; + } } private class Statistic : FillFlowContainer @@ -314,13 +318,13 @@ namespace osu.Game.Overlays.BeatmapSet Origin = Anchor.CentreLeft, Icon = icon, Shadow = true, - Size = new Vector2(13), + Size = new Vector2(12), }, text = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold, italics: true), }, }; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 53003b0488..e64256b850 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class HeaderDownloadButton : BeatmapDownloadTrackingComposite, IHasTooltip { + private const int text_size = 12; + private readonly bool noVideo; public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; @@ -80,8 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Icon = FontAwesome.Solid.Download, - Size = new Vector2(16), - Margin = new MarginPadding { Right = 5 }, + Size = new Vector2(18), }, } }, @@ -120,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons new OsuSpriteText { Text = "Downloading...", - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; break; @@ -131,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons new OsuSpriteText { Text = "Importing...", - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; break; @@ -146,12 +147,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons new OsuSpriteText { Text = "Download", - Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, new OsuSpriteText { Text = getVideoSuffixText(), - Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold) + Font = OsuFont.GetFont(size: text_size - 2, weight: FontWeight.Bold) }, }; this.FadeIn(200); diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 488e181fa2..680487ffbb 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Vertical = 10 }, + Padding = new MarginPadding { Vertical = 10 } }, }, new DetailBox diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 29f09a1ad8..c1e9ce2008 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -144,12 +144,15 @@ namespace osu.Game.Overlays.BeatmapSet }, } }, - artist = new OsuSpriteText { Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true) }, + artist = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + Margin = new MarginPadding { Bottom = 20 } + }, new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 20 }, Child = author = new AuthorInfo(), }, beatmapAvailability = new BeatmapAvailability(), diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 85e871baca..0f2d6a9e35 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -20,8 +20,9 @@ namespace osu.Game.Overlays.BeatmapSet public class Info : Container { private const float transition_duration = 250; - private const float metadata_width = 225; + private const float metadata_width = 175; private const float spacing = 20; + private const float base_height = 220; private readonly Box successRateBackground; private readonly Box background; @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet OsuSpriteText unrankedPlaceholder; RelativeSizeAxes = Axes.X; - Height = 220; + Height = base_height; Masking = true; EdgeEffect = new EdgeEffectParameters { @@ -135,6 +136,7 @@ namespace osu.Game.Overlays.BeatmapSet var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; unrankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; + Height = setHasLeaderboard ? 270 : base_height; }; } @@ -176,8 +178,8 @@ namespace osu.Game.Overlays.BeatmapSet new OsuSpriteText { Text = title, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), - Margin = new MarginPadding { Top = 20 }, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), + Margin = new MarginPadding { Top = 15 }, }, textFlow = new OsuTextFlowContainer { diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs index 20a3b09db4..607355b7bf 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs @@ -3,7 +3,6 @@ using osu.Game.Screens.Select.Leaderboards; using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics.UserInterface; @@ -37,7 +36,6 @@ namespace osu.Game.Overlays.BeatmapSet public ScopeSelectorTabItem(BeatmapLeaderboardScope value) : base(value) { - Text.Font = OsuFont.GetFont(size: 16); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index bb85b4a37b..0ae8a8bef5 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -17,13 +17,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private readonly Box background; - public DrawableTopScore(ScoreInfo score, int position = 1) + public DrawableTopScore(ScoreInfo score, int? position = 1) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; - CornerRadius = 5; + CornerRadius = 4; EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Vertical = 10, Left = 10, - Right = 25, + Right = 30, }, Children = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs index ba08a78a61..b2c87a1477 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/NotSupporterPlaceholder.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), + Spacing = new Vector2(0, 20), Children = new Drawable[] { new OsuSpriteText @@ -29,9 +29,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = @"You need to be an osu!supporter to access the friend and country rankings!", - Font = OsuFont.GetFont(weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), }, - text = new LinkFlowContainer(t => t.Font = t.Font.With(size: 12)) + text = new LinkFlowContainer(t => t.Font = t.Font.With(size: 11)) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 43a45bd2fc..f1250679c1 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public class ScoreTable : TableContainer { private const float horizontal_inset = 20; - private const float row_height = 25; + private const float row_height = 22; private const int text_size = 12; private readonly FillFlowContainer backgroundFlow; @@ -63,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; for (int i = 0; i < value.Count; i++) - backgroundFlow.Add(new ScoreTableRowBackground(i, value[i])); + backgroundFlow.Add(new ScoreTableRowBackground(i, value[i], row_height)); Columns = createHeaders(value[0]); Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); @@ -77,17 +77,20 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 60, maxSize: 70)), - new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)), - new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 110)) + new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)), + new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag + new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)), + new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) }; - foreach (var statistic in score.SortedStatistics) - columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70))); + foreach (var statistic in score.SortedStatistics.Take(score.SortedStatistics.Count() - 1)) + columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + + columns.Add(new TableColumn(score.SortedStatistics.LastOrDefault().Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 45, maxSize: 95))); columns.AddRange(new[] { - new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70)), + new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30)), new TableColumn("mods", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), }); @@ -96,6 +99,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private Drawable[] createContent(int index, ScoreInfo score) { + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(score.User); + var content = new List { new OsuSpriteText @@ -105,7 +111,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }, new UpdateableRank(score.Rank) { - Size = new Vector2(30, 20) + Size = new Vector2(28, 14) }, new OsuSpriteText { @@ -120,35 +126,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Font = OsuFont.GetFont(size: text_size), Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, - }; - - var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; - username.AddUserLink(score.User); - - content.AddRange(new Drawable[] - { - new FillFlowContainer + new UpdateableFlag(score.User.Country) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Right = horizontal_inset }, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new UpdateableFlag(score.User.Country) - { - Size = new Vector2(20, 13), - ShowPlaceholderOnNull = false, - }, - username - } + Size = new Vector2(19, 13), + ShowPlaceholderOnNull = false, }, + username, new OsuSpriteText { Text = $@"{score.MaxCombo:N0}x", - Font = OsuFont.GetFont(size: text_size) + Font = OsuFont.GetFont(size: text_size), + Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White } - }); + }; foreach (var kvp in score.SortedStatistics) { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs index 83271efe09..d84e1eff8c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -22,13 +22,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly int index; private readonly ScoreInfo score; - public ScoreTableRowBackground(int index, ScoreInfo score) + public ScoreTableRowBackground(int index, ScoreInfo score, float height) { this.index = index; this.score = score; RelativeSizeAxes = Axes.X; - Height = 25; + Height = height; CornerRadius = 5; Masking = true; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2d8bd10b13..92ff3c3125 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -90,9 +90,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Width = 0.95f, Direction = FillDirection.Vertical, - Margin = new MarginPadding { Vertical = spacing }, + Padding = new MarginPadding { Horizontal = 50 }, + Margin = new MarginPadding { Vertical = 20 }, Children = new Drawable[] { new FillFlowContainer @@ -121,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Vertical = spacing }, + Margin = new MarginPadding { Top = spacing }, Children = new Drawable[] { noScoresPlaceholder = new NoScoresPlaceholder diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index a7066c4827..a15dc57d23 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private const float bottom_columns_min_width = 45; private readonly FontUsage smallFont = OsuFont.GetFont(size: 16); - private readonly FontUsage largeFont = OsuFont.GetFont(size: 22); + private readonly FontUsage largeFont = OsuFont.GetFont(size: 22, weight: FontWeight.Light); private readonly TextColumn totalScoreColumn; private readonly TextColumn accuracyColumn; @@ -47,7 +47,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(10, 8), Children = new Drawable[] { new FillFlowContainer @@ -117,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public InfoColumn(string title, Drawable content, float? minWidth = null) { AutoSizeAxes = Axes.Both; + Margin = new MarginPadding { Vertical = 5 }; InternalChild = new GridContainer { @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 4), + new Dimension(GridSizeMode.Absolute, 2), new Dimension(GridSizeMode.AutoSize) }, Content = new[] @@ -138,21 +138,24 @@ namespace osu.Game.Overlays.BeatmapSet.Scores text = new OsuSpriteText { Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold), - Text = title.ToUpper() + Text = title.ToUpper(), + // 2px padding bottom + 1px vertical to compensate for the additional spacing because of 1.25 line-height in osu-web + Padding = new MarginPadding { Top = 1, Bottom = 3 } } }, new Drawable[] { separator = new Box { - Anchor = Anchor.CentreLeft, + Anchor = Anchor.TopLeft, RelativeSizeAxes = Axes.X, - Height = 2 - } + Height = 2, + }, }, new[] { - content + // osu-web has 4px margin here but also uses 0.9 line-height, reducing margin to 2px seems like a good alternative to that + content.With(c => c.Margin = new MarginPadding { Top = 2 }) } } }; @@ -194,9 +197,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public ModsInfoColumn() : this(new FillFlowContainer { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, Spacing = new Vector2(1), + Height = 18f }) { } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 8a368aa535..9111a0cfc7 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.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 osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,7 +13,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using osu.Game.Users.Drawables; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly UpdateableRank rank; private readonly UpdateableAvatar avatar; private readonly LinkFlowContainer usernameText; - private readonly SpriteText date; + private readonly DrawableDate achievedOn; private readonly UpdateableFlag flag; public TopScoreUserSection() @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.Centre, Size = new Vector2(70), Masking = true, - CornerRadius = 5, + CornerRadius = 4, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -92,11 +92,24 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, }, - date = new OsuSpriteText + new FillFlowContainer { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 10) + Children = new[] + { + new OsuSpriteText + { + Text = "achieved ", + Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold) + }, + achievedOn = new DrawableDate(DateTimeOffset.MinValue) + { + Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold) + }, + } }, flag = new UpdateableFlag { @@ -112,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }; } - public int ScorePosition + public int? ScorePosition { - set => rankText.Text = $"#{value}"; + set => rankText.Text = value == null ? "-" : $"#{value}"; } /// @@ -126,7 +139,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { avatar.User = value.User; flag.Country = value.User.Country; - date.Text = $@"achieved {HumanizerUtils.Humanize(value.Date)}"; + achievedOn.Date = value.Date; usernameText.Clear(); usernameText.AddUserLink(value.User); diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 15216b6e69..3bb36545cd 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Success Rate", - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12) }, successRate = new Bar { @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.TopRight, Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 13), + Font = OsuFont.GetFont(size: 12), }, }, new OsuSpriteText @@ -90,7 +90,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Points of Failure", - Font = OsuFont.GetFont(size: 13), + Font = OsuFont.GetFont(size: 12), Margin = new MarginPadding { Vertical = 20 }, }, }, diff --git a/osu.Game/Overlays/Comments/CancellableCommentEditor.cs b/osu.Game/Overlays/Comments/CancellableCommentEditor.cs new file mode 100644 index 0000000000..c226b7f07f --- /dev/null +++ b/osu.Game/Overlays/Comments/CancellableCommentEditor.cs @@ -0,0 +1,71 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Comments +{ + public abstract class CancellableCommentEditor : CommentEditor + { + public Action OnCancel; + + [BackgroundDependencyLoader] + private void load() + { + ButtonsContainer.Add(new CancelButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Action = () => OnCancel?.Invoke() + }); + } + + private class CancelButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { background }; + + private readonly Box background; + + public CancelButton() + { + AutoSizeAxes = Axes.Both; + Child = new CircularContainer + { + Masking = true, + Height = 25, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Margin = new MarginPadding { Horizontal = 20 }, + Text = @"Cancel" + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light4; + HoverColour = colourProvider.Light3; + } + } + } +} diff --git a/osu.Game/Overlays/Comments/CommentEditor.cs b/osu.Game/Overlays/Comments/CommentEditor.cs new file mode 100644 index 0000000000..2fa4cb68f3 --- /dev/null +++ b/osu.Game/Overlays/Comments/CommentEditor.cs @@ -0,0 +1,241 @@ +// 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.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osu.Game.Graphics.UserInterface; +using System.Collections.Generic; +using System; +using osuTK; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Comments +{ + public abstract class CommentEditor : CompositeDrawable + { + private const int side_padding = 8; + + public Action OnCommit; + + public bool IsLoading + { + get => commitButton.IsLoading; + set => commitButton.IsLoading = value; + } + + protected abstract string FooterText { get; } + + protected abstract string CommitButtonText { get; } + + protected abstract string TextBoxPlaceholder { get; } + + protected FillFlowContainer ButtonsContainer { get; private set; } + + protected readonly Bindable Current = new Bindable(); + + private CommitButton commitButton; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + EditorTextBox textBox; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + BorderThickness = 3; + BorderColour = colourProvider.Background3; + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textBox = new EditorTextBox + { + Height = 40, + RelativeSizeAxes = Axes.X, + PlaceholderText = TextBoxPlaceholder, + Current = Current + }, + new Container + { + Name = "Footer", + RelativeSizeAxes = Axes.X, + Height = 35, + Padding = new MarginPadding { Horizontal = side_padding }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = FooterText + }, + ButtonsContainer = new FillFlowContainer + { + Name = "Buttons", + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Child = commitButton = new CommitButton(CommitButtonText) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Action = () => + { + OnCommit?.Invoke(Current.Value); + Current.Value = string.Empty; + } + } + } + } + } + } + } + }); + + textBox.OnCommit += (u, v) => + { + if (commitButton.IsBlocked.Value) + return; + + commitButton.Click(); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(text => commitButton.IsBlocked.Value = string.IsNullOrEmpty(text.NewValue), true); + } + + private class EditorTextBox : BasicTextBox + { + protected override float LeftRightPadding => side_padding; + + protected override Color4 SelectionColour => Color4.Gray; + + private OsuSpriteText placeholder; + + public EditorTextBox() + { + Masking = false; + TextContainer.Height = 0.4f; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundUnfocused = BackgroundFocused = colourProvider.Background5; + placeholder.Colour = colourProvider.Background3; + BackgroundCommit = colourProvider.Background3; + } + + protected override SpriteText CreatePlaceholder() => placeholder = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + }; + + protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }; + } + + private class CommitButton : LoadingButton + { + private const int duration = 200; + + public readonly BindableBool IsBlocked = new BindableBool(); + + public override bool PropagatePositionalInputSubTree => !IsBlocked.Value && base.PropagatePositionalInputSubTree; + + protected override IEnumerable EffectTargets => new[] { background }; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private OsuSpriteText drawableText; + private Box background; + private Box blockedBackground; + + public CommitButton(string text) + { + AutoSizeAxes = Axes.Both; + LoadingAnimationSize = new Vector2(10); + + drawableText.Text = text; + } + + [BackgroundDependencyLoader] + private void load() + { + IdleColour = colourProvider.Light4; + HoverColour = colourProvider.Light3; + blockedBackground.Colour = colourProvider.Background5; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + IsBlocked.BindValueChanged(onBlockedStateChanged, true); + } + + private void onBlockedStateChanged(ValueChangedEvent isBlocked) + { + drawableText.FadeColour(isBlocked.NewValue ? colourProvider.Foreground1 : Color4.White, duration, Easing.OutQuint); + background.FadeTo(isBlocked.NewValue ? 0 : 1, duration, Easing.OutQuint); + } + + protected override Drawable CreateContent() => new CircularContainer + { + Masking = true, + Height = 25, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + blockedBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + }, + drawableText = new OsuSpriteText + { + AlwaysPresent = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Margin = new MarginPadding { Horizontal = 20 } + } + } + }; + + protected override void OnLoadStarted() => drawableText.FadeOut(duration, Easing.OutQuint); + + protected override void OnLoadFinished() => drawableText.FadeIn(duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 8b04bf0387..70a3ab54fb 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -34,14 +34,13 @@ namespace osu.Game.Overlays.Direct public enum DirectSortCriteria { - Relevance, Title, Artist, - Creator, Difficulty, Ranked, Rating, Plays, Favourites, + Relevance, } } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 8cafbc694a..de2f916946 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -26,8 +26,9 @@ namespace osu.Game.Overlays.Music private TextFlowContainer text; private IEnumerable titleSprites; - private ILocalisedBindableString titleBind; - private ILocalisedBindableString artistBind; + + private ILocalisedBindableString title; + private ILocalisedBindableString artist; private Color4 selectedColour; private Color4 artistColour; @@ -47,24 +48,24 @@ namespace osu.Game.Overlays.Music artistColour = colours.Gray9; HandleColour = colours.Gray5; - titleBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title))); - artistBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist))); + title = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title))); + artist = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist))); } protected override void LoadComplete() { base.LoadComplete(); + artist.BindValueChanged(_ => recreateText(), true); + SelectedSet.BindValueChanged(set => { - if (set.OldValue != Model && set.NewValue != Model) + if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) return; foreach (Drawable s in titleSprites) - s.FadeColour(set.NewValue == Model ? selectedColour : Color4.White, FADE_DURATION); + s.FadeColour(set.NewValue.Equals(Model) ? selectedColour : Color4.White, FADE_DURATION); }, true); - - artistBind.BindValueChanged(_ => recreateText(), true); } protected override Drawable CreateContent() => text = new OsuTextFlowContainer @@ -78,9 +79,9 @@ namespace osu.Game.Overlays.Music text.Clear(); //space after the title to put a space between the title and artist - titleSprites = text.AddText(titleBind.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); + titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); - text.AddText(artistBind.Value, sprite => + text.AddText(artist.Value, sprite => { sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); sprite.Colour = artistColour; diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 8f753fd3aa..b878aba489 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -75,8 +75,6 @@ namespace osu.Game.Overlays.Music }, }; - list.Items.BindTo(beatmapSets); - filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); @@ -87,7 +85,13 @@ namespace osu.Game.Overlays.Music beatmap.Value.Track.Restart(); } }; + } + protected override void LoadComplete() + { + base.LoadComplete(); + + list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 7c7daf6eb9..d788929739 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -25,7 +25,16 @@ namespace osu.Game.Overlays [Resolved] private BeatmapManager beatmaps { get; set; } - public IBindableList BeatmapSets => beatmapSets; + public IBindableList BeatmapSets + { + get + { + if (LoadState < LoadState.Ready) + throw new InvalidOperationException($"{nameof(BeatmapSets)} should not be accessed before the music controller is loaded."); + + return beatmapSets; + } + } /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. @@ -54,16 +63,18 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; + + beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next())); } protected override void LoadComplete() { + base.LoadComplete(); + beatmap.BindValueChanged(beatmapChanged, true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); - base.LoadComplete(); } /// @@ -82,11 +93,16 @@ namespace osu.Game.Overlays /// public bool IsPlaying => current?.Track.IsRunning ?? false; - private void handleBeatmapAdded(BeatmapSetInfo set) => - Schedule(() => beatmapSets.Add(set)); + private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => + { + if (!beatmapSets.Contains(set)) + beatmapSets.Add(set); + }); - private void handleBeatmapRemoved(BeatmapSetInfo set) => - Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); + private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => + { + beatmapSets.RemoveAll(s => s.ID == set.ID); + }); private ScheduledDelegate seekDelegate; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index dfcf99d30c..118cb037cb 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -58,6 +58,9 @@ namespace osu.Game.Overlays [Resolved] private Bindable beatmap { get; set; } + [Resolved] + private OsuColour colours { get; set; } + public NowPlayingOverlay() { Width = 400; @@ -65,7 +68,7 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Children = new Drawable[] { @@ -182,15 +185,15 @@ namespace osu.Game.Overlays } } }; - - playlist.BeatmapSets.BindTo(musicController.BeatmapSets); - playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } protected override void LoadComplete() { base.LoadComplete(); + playlist.BeatmapSets.BindTo(musicController.BeatmapSets); + playlist.State.BindValueChanged(s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint), true); + beatmap.BindDisabledChanged(beatmapDisabledChanged, true); musicController.TrackChanged += trackChanged; diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 2674b3a81e..a89360bd3c 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -11,23 +11,21 @@ namespace osu.Game.Overlays.Rankings { public class RankingsOverlayHeader : TabControlOverlayHeader { - public readonly Bindable Ruleset = new Bindable(); - public readonly Bindable Country = new Bindable(); + public Bindable Ruleset => rulesetSelector.Current; + + public Bindable Country => countryFilter.Current; + + private OverlayRulesetSelector rulesetSelector; + private CountryFilter countryFilter; protected override ScreenTitle CreateTitle() => new RankingsTitle { Scope = { BindTarget = Current } }; - protected override Drawable CreateTitleContent() => new OverlayRulesetSelector - { - Current = Ruleset - }; + protected override Drawable CreateTitleContent() => rulesetSelector = new OverlayRulesetSelector(); - protected override Drawable CreateContent() => new CountryFilter - { - Current = Country - }; + protected override Drawable CreateContent() => countryFilter = new CountryFilter(); private class RankingsTitle : ScreenTitle { diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 32ac1404bc..0b9a48ce0e 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Rankings.Tables { @@ -61,18 +62,35 @@ namespace osu.Game.Overlays.Rankings.Tables } }; - private class CountryName : OsuSpriteText + private class CountryName : OsuHoverContainer { + protected override IEnumerable EffectTargets => new[] { text }; + + [Resolved(canBeNull: true)] + private RankingsOverlay rankings { get; set; } + + private readonly OsuSpriteText text; + private readonly Country country; + public CountryName(Country country) { - Font = OsuFont.GetFont(size: 12); - Text = country.FullName ?? string.Empty; + this.country = country; + + AutoSizeAxes = Axes.Both; + Add(text = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = country.FullName ?? string.Empty, + }); } [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - Colour = colourProvider.Light2; + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Content2; + + Action = () => rankings?.ShowCountry(country); } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index f3215d07fa..2c5ea61315 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -19,14 +19,17 @@ namespace osu.Game.Overlays { public class RankingsOverlay : FullscreenOverlay { - protected readonly Bindable Country = new Bindable(); - protected readonly Bindable Scope = new Bindable(); - private readonly Bindable ruleset = new Bindable(); + protected Bindable Country => header.Country; + + protected Bindable Scope => header.Current; + + private Bindable ruleset => header.Ruleset; private readonly BasicScrollContainer scrollFlow; private readonly Container contentContainer; private readonly DimmedLoadingLayer loading; private readonly Box background; + private readonly RankingsOverlayHeader header; private APIRequest lastRequest; private CancellationTokenSource cancellationToken; @@ -54,14 +57,11 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - new RankingsOverlayHeader + header = new RankingsOverlayHeader { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Depth = -float.MaxValue, - Country = { BindTarget = Country }, - Current = { BindTarget = Scope }, - Ruleset = { BindTarget = ruleset } + Depth = -float.MaxValue }, new Container { @@ -94,6 +94,8 @@ namespace osu.Game.Overlays protected override void LoadComplete() { + base.LoadComplete(); + Country.BindValueChanged(_ => { // if a country is requested, force performance scope. @@ -101,7 +103,7 @@ namespace osu.Game.Overlays Scope.Value = RankingsScope.Performance; Scheduler.AddOnce(loadNewContent); - }, true); + }); Scope.BindValueChanged(_ => { @@ -110,7 +112,7 @@ namespace osu.Game.Overlays Country.Value = null; Scheduler.AddOnce(loadNewContent); - }, true); + }); ruleset.BindValueChanged(_ => { @@ -118,9 +120,7 @@ namespace osu.Game.Overlays return; Scheduler.AddOnce(loadNewContent); - }, true); - - base.LoadComplete(); + }); } public void ShowCountry(Country requested) @@ -158,8 +158,8 @@ namespace osu.Game.Overlays return; } - request.Success += () => loadContent(createTableFromResponse(request)); - request.Failure += _ => loadContent(null); + request.Success += () => Schedule(() => loadContent(createTableFromResponse(request))); + request.Failure += _ => Schedule(() => loadContent(null)); api.Queue(request); } @@ -221,5 +221,13 @@ namespace osu.Game.Overlays contentContainer.Child = loaded; }, (cancellationToken = new CancellationTokenSource()).Token); } + + protected override void Dispose(bool isDisposing) + { + lastRequest?.Cancel(); + cancellationToken?.Cancel(); + + base.Dispose(isDisposing); + } } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 6f20bcf595..67fe18e8dd 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (HitObject is IHasComboInformation combo) { comboIndexBindable = combo.ComboIndexBindable.GetBoundCopy(); - comboIndexBindable.BindValueChanged(_ => updateAccentColour(), true); + comboIndexBindable.BindValueChanged(_ => updateComboColour(), true); } samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); @@ -336,7 +336,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { base.SkinChanged(skin, allowFallback); - updateAccentColour(); + updateComboColour(); ApplySkin(skin, allowFallback); @@ -344,13 +344,29 @@ namespace osu.Game.Rulesets.Objects.Drawables updateState(State.Value, true); } - private void updateAccentColour() + private void updateComboColour() { - if (HitObject is IHasComboInformation combo) - { - var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; - AccentColour.Value = comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; - } + if (!(HitObject is IHasComboInformation)) return; + + var comboColours = CurrentSkin.GetConfig>(GlobalSkinColours.ComboColours)?.Value; + + AccentColour.Value = GetComboColour(comboColours); + } + + /// + /// Called to retrieve the combo colour. Automatically assigned to . + /// Defaults to using to decide on a colour. + /// + /// + /// This will only be called if the implements . + /// + /// A list of combo colours provided by the beatmap or skin. Can be null if not available. + protected virtual Color4 GetComboColour(IReadOnlyList comboColours) + { + if (!(HitObject is IHasComboInformation combo)) + throw new InvalidOperationException($"{nameof(HitObject)} must implement {nameof(IHasComboInformation)}"); + + return comboColours?.Count > 0 ? comboColours[combo.ComboIndex % comboColours.Count] : Color4.White; } /// diff --git a/osu.Game/Screens/Multi/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/Multi/Components/MatchBeatmapDetailArea.cs index 8e085d6979..2c5fd2d397 100644 --- a/osu.Game/Screens/Multi/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/Multi/Components/MatchBeatmapDetailArea.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Multi.Components { new TriangleButton { - Text = "create new item", + Text = "Add new playlist entry", RelativeSizeAxes = Axes.Both, Size = Vector2.One, Action = () => CreateNewItem?.Invoke() diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs similarity index 59% rename from osu.Game/Screens/Multi/Match/Components/OverlinedDisplay.cs rename to osu.Game/Screens/Multi/Components/OverlinedDisplay.cs index 854877bd1c..71cabd8b50 100644 --- a/osu.Game/Screens/Multi/Match/Components/OverlinedDisplay.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs @@ -9,12 +9,32 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; -namespace osu.Game.Screens.Multi.Match.Components +namespace osu.Game.Screens.Multi.Components { public abstract class OverlinedDisplay : MultiplayerComposite { protected readonly Container Content; + public override Axes RelativeSizeAxes + { + get => base.RelativeSizeAxes; + set + { + base.RelativeSizeAxes = value; + updateDimensions(); + } + } + + public override Axes AutoSizeAxes + { + get => base.AutoSizeAxes; + protected set + { + base.AutoSizeAxes = value; + updateDimensions(); + } + } + protected string Details { set => details.Text = value; @@ -22,14 +42,12 @@ namespace osu.Game.Screens.Multi.Match.Components private readonly Circle line; private readonly OsuSpriteText details; + private readonly GridContainer grid; protected OverlinedDisplay(string title) { - RelativeSizeAxes = Axes.Both; - - InternalChild = new GridContainer + InternalChild = grid = new GridContainer { - RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] @@ -62,19 +80,12 @@ namespace osu.Game.Screens.Multi.Match.Components }, new Drawable[] { - Content = new Container - { - Margin = new MarginPadding { Top = 5 }, - RelativeSizeAxes = Axes.Both - } + Content = new Container { Margin = new MarginPadding { Top = 5 } } } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), } }; + + updateDimensions(); } [BackgroundDependencyLoader] @@ -83,5 +94,23 @@ namespace osu.Game.Screens.Multi.Match.Components line.Colour = colours.Yellow; details.Colour = colours.Yellow; } + + private void updateDimensions() + { + grid.RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(AutoSizeAxes.HasFlag(Axes.Y) ? GridSizeMode.AutoSize : GridSizeMode.Distributed), + }; + + // Assigning to none is done so that setting auto and relative size modes doesn't cause exceptions to be thrown + grid.AutoSizeAxes = Content.AutoSizeAxes = Axes.None; + grid.RelativeSizeAxes = Content.RelativeSizeAxes = Axes.None; + + // Auto-size when required, otherwise eagerly relative-size + grid.AutoSizeAxes = Content.AutoSizeAxes = AutoSizeAxes; + grid.RelativeSizeAxes = Content.RelativeSizeAxes = ~AutoSizeAxes; + } } } diff --git a/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs b/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs new file mode 100644 index 0000000000..a709c6a57a --- /dev/null +++ b/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs @@ -0,0 +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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Multi.Components +{ + public class OverlinedParticipants : OverlinedDisplay + { + public new Axes AutoSizeAxes + { + get => base.AutoSizeAxes; + set => base.AutoSizeAxes = value; + } + + public OverlinedParticipants(Direction direction) + : base("Participants") + { + OsuScrollContainer scroll; + ParticipantsList list; + + Content.Add(scroll = new OsuScrollContainer(direction) + { + Child = list = new ParticipantsList() + }); + + switch (direction) + { + case Direction.Horizontal: + scroll.RelativeSizeAxes = Axes.X; + scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2; + list.AutoSizeAxes = Axes.Both; + break; + + case Direction.Vertical: + scroll.RelativeSizeAxes = Axes.Both; + list.RelativeSizeAxes = Axes.X; + list.AutoSizeAxes = Axes.Y; + break; + } + } + + [BackgroundDependencyLoader] + private void load() + { + ParticipantCount.BindValueChanged(_ => setParticipantCount()); + MaxParticipants.BindValueChanged(_ => setParticipantCount()); + + setParticipantCount(); + } + + private void setParticipantCount() => Details = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedPlaylist.cs b/osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs similarity index 95% rename from osu.Game/Screens/Multi/Match/Components/OverlinedPlaylist.cs rename to osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs index eea85d9d64..4fe79b40a0 100644 --- a/osu.Game/Screens/Multi/Match/Components/OverlinedPlaylist.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs @@ -6,7 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online.Multiplayer; -namespace osu.Game.Screens.Multi.Match.Components +namespace osu.Game.Screens.Multi.Components { public class OverlinedPlaylist : OverlinedDisplay { diff --git a/osu.Game/Screens/Multi/Components/ParticipantsList.cs b/osu.Game/Screens/Multi/Components/ParticipantsList.cs index 2ef36b2795..e383e0414b 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantsList.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsList.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Users; @@ -19,21 +18,39 @@ namespace osu.Game.Screens.Multi.Components { public class ParticipantsList : MultiplayerComposite { + public const float TILE_SIZE = 35; + + public override Axes RelativeSizeAxes + { + get => base.RelativeSizeAxes; + set + { + base.RelativeSizeAxes = value; + fill.RelativeSizeAxes = value; + } + } + + public new Axes AutoSizeAxes + { + get => base.AutoSizeAxes; + set + { + base.AutoSizeAxes = value; + fill.AutoSizeAxes = value; + } + } + + public FillDirection Direction + { + get => fill.Direction; + set => fill.Direction = value; + } + private readonly FillFlowContainer fill; public ParticipantsList() { - InternalChild = new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = fill = new FillFlowContainer - { - Spacing = new Vector2(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - } - }; + InternalChild = fill = new FillFlowContainer { Spacing = new Vector2(10) }; } [BackgroundDependencyLoader] @@ -96,7 +113,7 @@ namespace osu.Game.Screens.Multi.Components public UserTile(User user) { this.user = user; - Size = new Vector2(70f); + Size = new Vector2(TILE_SIZE); CornerRadius = 5f; Masking = true; diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index ca85aec4e4..a7ed1f5846 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Multi private readonly bool allowEdit; private readonly bool allowSelection; - protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || SelectedItem.Value == Model; + protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection) : base(item) diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 9f93afec9d..300418441e 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -4,8 +4,8 @@ using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Threading; -using osu.Game.Graphics; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; using osuTK.Graphics; @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { public class FilterControl : SearchableListFilterControl { - protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42"); + protected override Color4 BackgroundColour => Color4.Black.Opacity(0.5f); protected override PrimaryFilter DefaultTab => PrimaryFilter.Open; protected override SecondaryFilter DefaultCategory => SecondaryFilter.Public; diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs new file mode 100644 index 0000000000..02f2667802 --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs @@ -0,0 +1,89 @@ +// 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 osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class RoomInfo : MultiplayerComposite + { + private readonly List statusElements = new List(); + private readonly SpriteText roomName; + + public RoomInfo() + { + AutoSizeAxes = Axes.Y; + + RoomStatusInfo statusInfo; + ModeTypeInfo typeInfo; + ParticipantInfo participantInfo; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + roomName = new OsuSpriteText { Font = OsuFont.GetFont(size: 30) }, + statusInfo = new RoomStatusInfo(), + } + }, + typeInfo = new ModeTypeInfo + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight + } + } + }, + participantInfo = new ParticipantInfo(), + } + }; + + statusElements.AddRange(new Drawable[] { statusInfo, typeInfo, participantInfo }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (RoomID.Value == null) + statusElements.ForEach(e => e.FadeOut()); + + RoomID.BindValueChanged(id => + { + if (id.NewValue == null) + statusElements.ForEach(e => e.FadeOut(100)); + else + statusElements.ForEach(e => e.FadeIn(100)); + }, true); + + RoomName.BindValueChanged(name => + { + roomName.Text = name.NewValue ?? "No room selected"; + }, true); + } + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index cb6bbf6731..891853dee5 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -2,18 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; -using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Multi.Lounge.Components @@ -24,16 +18,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 }; - private ParticipantCountDisplay participantCount; - private OsuSpriteText name; - private BeatmapTypeInfo beatmapTypeInfo; - private ParticipantInfo participantInfo; - [Resolved] private BeatmapManager beatmaps { get; set; } - private readonly Bindable status = new Bindable(new RoomStatusNoneSelected()); - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -42,177 +29,52 @@ namespace osu.Game.Screens.Multi.Lounge.Components new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"343138"), + Colour = Color4.Black, + Alpha = 0.25f }, - new GridContainer + new Container { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Padding = new MarginPadding { Horizontal = 30 }, + Child = new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Distributed), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Content = new[] { - new FillFlowContainer + new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 200, - Masking = true, - Children = new Drawable[] + new RoomInfo { - new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Children = new Drawable[] - { - participantCount = new ParticipantCountDisplay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - name = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 30), - Current = RoomName - }, - }, - }, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding { Vertical = 60 }, }, - }, - new StatusColouredContainer(transition_duration) - { - RelativeSizeAxes = Axes.X, - Height = 5, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + new OverlinedParticipants(Direction.Horizontal) { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, - Padding = contentPadding, - Spacing = new Vector2(0f, 5f), - Children = new Drawable[] - { - new StatusColouredContainer(transition_duration) - { - AutoSizeAxes = Axes.Both, - Child = new StatusText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), - } - }, - beatmapTypeInfo = new BeatmapTypeInfo(), - }, - }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = contentPadding, - Children = new Drawable[] - { - participantInfo = new ParticipantInfo(), - }, - }, - }, + } + } + }, + new Drawable[] + { + new OverlinedPlaylist(false) { RelativeSizeAxes = Axes.Both }, }, }, - new Drawable[] + RowDimensions = new[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 10 }, - Child = new ParticipantsList { RelativeSizeAxes = Axes.Both } - } + new Dimension(GridSizeMode.AutoSize), } } } }; - - Status.BindValueChanged(_ => updateStatus(), true); - RoomID.BindValueChanged(_ => updateStatus(), true); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(status, new CacheInfo(nameof(Room.Status), typeof(Room))); - return dependencies; - } - - private void updateStatus() - { - if (RoomID.Value == null) - { - status.Value = new RoomStatusNoneSelected(); - - participantCount.FadeOut(transition_duration); - beatmapTypeInfo.FadeOut(transition_duration); - name.FadeOut(transition_duration); - participantInfo.FadeOut(transition_duration); - } - else - { - status.Value = Status.Value; - - participantCount.FadeIn(transition_duration); - beatmapTypeInfo.FadeIn(transition_duration); - name.FadeIn(transition_duration); - participantInfo.FadeIn(transition_duration); - } - } - - private class RoomStatusNoneSelected : RoomStatus - { - public override string Message => @"No Room Selected"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8; - } - - private class StatusText : OsuSpriteText - { - [Resolved(typeof(Room), nameof(Room.Status))] - private Bindable status { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - status.BindValueChanged(s => Text = s.NewValue.Message, true); - } } } } diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 3709b85fcb..58e4548ee2 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Multi.Lounge public LoungeSubScreen() { + SearchContainer searchContainer; + InternalChildren = new Drawable[] { Filter = new FilterControl { Depth = -1 }, @@ -49,14 +51,14 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = new SearchContainer + Child = searchContainer = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = new RoomsContainer { JoinRequested = joinRequested } }, }, - processingOverlay = new ProcessingOverlay { Alpha = 0 } + processingOverlay = new ProcessingOverlay(searchContainer), } }, new RoomInspector diff --git a/osu.Game/Screens/Multi/Match/Components/Footer.cs b/osu.Game/Screens/Multi/Match/Components/Footer.cs index 93430d9131..c0c866d815 100644 --- a/osu.Game/Screens/Multi/Match/Components/Footer.cs +++ b/osu.Game/Screens/Multi/Match/Components/Footer.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osuTK; @@ -22,7 +21,6 @@ namespace osu.Game.Screens.Multi.Match.Components public readonly Bindable SelectedItem = new Bindable(); private readonly Drawable background; - private readonly OsuButton startButton; public Footer() { @@ -32,7 +30,7 @@ namespace osu.Game.Screens.Multi.Match.Components InternalChildren = new[] { background = new Box { RelativeSizeAxes = Axes.Both }, - startButton = new ReadyButton + new ReadyButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -47,7 +45,6 @@ namespace osu.Game.Screens.Multi.Match.Components private void load(OsuColour colours) { background.Colour = OsuColour.FromHex(@"28242d"); - startButton.BackgroundColour = colours.Green; } } } diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index e3110cdfc8..8c005a2647 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -79,226 +79,235 @@ namespace osu.Game.Screens.Multi.Match.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container dimContent; + InternalChildren = new Drawable[] { - new Box + dimContent = new Container { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Children = new Drawable[] { - new Dimension(GridSizeMode.Distributed), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + new Box { - new OsuScrollContainer + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - Padding = new MarginPadding + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING, - Vertical = 10 - }, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new Container + new OsuScrollContainer { - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + Padding = new MarginPadding { - new SectionContainer + Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING, + Vertical = 10 + }, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Container { - Padding = new MarginPadding { Right = field_padding / 2 }, - Children = new[] + Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - new Section("Room name") + new SectionContainer { - Child = NameField = new SettingsTextBox + Padding = new MarginPadding { Right = field_padding / 2 }, + Children = new[] { - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - OnCommit = (sender, text) => apply(), - }, - }, - new Section("Duration") - { - Child = DurationField = new DurationDropdown - { - RelativeSizeAxes = Axes.X, - Items = new[] + new Section("Room name") { - TimeSpan.FromMinutes(30), - TimeSpan.FromHours(1), - TimeSpan.FromHours(2), - TimeSpan.FromHours(4), - TimeSpan.FromHours(8), - TimeSpan.FromHours(12), - //TimeSpan.FromHours(16), - TimeSpan.FromHours(24), - TimeSpan.FromDays(3), - TimeSpan.FromDays(7) - } - } - }, - new Section("Room visibility") - { - Alpha = disabled_alpha, - Child = AvailabilityPicker = new RoomAvailabilityPicker - { - Enabled = { Value = false } - }, - }, - new Section("Game type") - { - Alpha = disabled_alpha, - Child = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Spacing = new Vector2(7), - Children = new Drawable[] - { - TypePicker = new GameTypePicker + Child = NameField = new SettingsTextBox { RelativeSizeAxes = Axes.X, - Enabled = { Value = false } - }, - typeLabel = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14), - Colour = colours.Yellow + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), }, }, - }, - }, - new Section("Max participants") - { - Alpha = disabled_alpha, - Child = MaxParticipantsField = new SettingsNumberTextBox - { - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - ReadOnly = true, - OnCommit = (sender, text) => apply() - }, - }, - new Section("Password (optional)") - { - Alpha = disabled_alpha, - Child = new SettingsPasswordTextBox - { - RelativeSizeAxes = Axes.X, - TabbableContentContainer = this, - ReadOnly = true, - OnCommit = (sender, text) => apply() - }, - }, - }, - }, - new SectionContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Padding = new MarginPadding { Left = field_padding / 2 }, - Children = new[] - { - new Section("Playlist") - { - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - Height = 300, - Content = new[] + new Section("Duration") { - new Drawable[] + Child = DurationField = new DurationDropdown { - playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both } - }, - new Drawable[] - { - new OsuButton + RelativeSizeAxes = Axes.X, + Items = new[] { - RelativeSizeAxes = Axes.X, - Height = 40, - Text = "Edit playlist", - Action = () => EditPlaylist?.Invoke() + TimeSpan.FromMinutes(30), + TimeSpan.FromHours(1), + TimeSpan.FromHours(2), + TimeSpan.FromHours(4), + TimeSpan.FromHours(8), + TimeSpan.FromHours(12), + //TimeSpan.FromHours(16), + TimeSpan.FromHours(24), + TimeSpan.FromDays(3), + TimeSpan.FromDays(7) } } }, - RowDimensions = new[] + new Section("Room visibility") { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - } + Alpha = disabled_alpha, + Child = AvailabilityPicker = new RoomAvailabilityPicker + { + Enabled = { Value = false } + }, + }, + new Section("Game type") + { + Alpha = disabled_alpha, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Children = new Drawable[] + { + TypePicker = new GameTypePicker + { + RelativeSizeAxes = Axes.X, + Enabled = { Value = false } + }, + typeLabel = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14), + Colour = colours.Yellow + }, + }, + }, + }, + new Section("Max participants") + { + Alpha = disabled_alpha, + Child = MaxParticipantsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + ReadOnly = true, + OnCommit = (sender, text) => apply() + }, + }, + new Section("Password (optional)") + { + Alpha = disabled_alpha, + Child = new SettingsPasswordTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + ReadOnly = true, + OnCommit = (sender, text) => apply() + }, + }, + }, + }, + new SectionContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding { Left = field_padding / 2 }, + Children = new[] + { + new Section("Playlist") + { + Child = new GridContainer + { + RelativeSizeAxes = Axes.X, + Height = 300, + Content = new[] + { + new Drawable[] + { + playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both } + }, + new Drawable[] + { + new PurpleTriangleButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Text = "Edit playlist", + Action = () => EditPlaylist?.Invoke() + } + } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + } + }, + }, }, }, - }, + } }, - } - }, - }, - }, - new Drawable[] - { - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = 2, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d").Darken(0.5f).Opacity(1f), }, - new FillFlowContainer + }, + new Drawable[] + { + new Container { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Y = 2, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding { Vertical = 20 }, - Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - ApplyButton = new CreateRoomButton + new Box { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(230, 55), - Enabled = { Value = false }, - Action = apply, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d").Darken(0.5f).Opacity(1f), }, - ErrorText = new OsuSpriteText + new FillFlowContainer { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Alpha = 0, - Depth = 1, - Colour = colours.RedDark + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding { Vertical = 20 }, + Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Children = new Drawable[] + { + ApplyButton = new CreateRoomButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(230, 55), + Enabled = { Value = false }, + Action = apply, + }, + ErrorText = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Alpha = 0, + Depth = 1, + Colour = colours.RedDark + } + } } } } } } - } + }, } }, - processingOverlay = new ProcessingOverlay { Alpha = 0 } + processingOverlay = new ProcessingOverlay(dimContent) }; TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); @@ -447,10 +456,7 @@ namespace osu.Game.Screens.Multi.Match.Components Menu.MaxHeight = 100; } - protected override string GenerateItemText(TimeSpan item) - { - return item.Humanize(); - } + protected override string GenerateItemText(TimeSpan item) => item.Humanize(); } } } diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedParticipants.cs b/osu.Game/Screens/Multi/Match/Components/OverlinedParticipants.cs deleted file mode 100644 index 7a4290a9a1..0000000000 --- a/osu.Game/Screens/Multi/Match/Components/OverlinedParticipants.cs +++ /dev/null @@ -1,29 +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; -using osu.Game.Screens.Multi.Components; - -namespace osu.Game.Screens.Multi.Match.Components -{ - public class OverlinedParticipants : OverlinedDisplay - { - public OverlinedParticipants() - : base("Participants") - { - Content.Add(new ParticipantsList { RelativeSizeAxes = Axes.Both }); - } - - [BackgroundDependencyLoader] - private void load() - { - ParticipantCount.BindValueChanged(_ => setParticipantCount()); - MaxParticipants.BindValueChanged(_ => setParticipantCount()); - - setParticipantCount(); - } - - private void setParticipantCount() => Details = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); - } -} diff --git a/osu.Game/Screens/Multi/Match/Components/PurpleTriangleButton.cs b/osu.Game/Screens/Multi/Match/Components/PurpleTriangleButton.cs new file mode 100644 index 0000000000..8a0369ceba --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/PurpleTriangleButton.cs @@ -0,0 +1,20 @@ +// 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.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class PurpleTriangleButton : TriangleButton + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundColour = OsuColour.FromHex(@"593790"); + Triangles.ColourLight = OsuColour.FromHex(@"7247b6"); + Triangles.ColourDark = OsuColour.FromHex(@"593790"); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index d39217db5d..8f484d3672 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -6,12 +6,13 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; namespace osu.Game.Screens.Multi.Match.Components { - public class ReadyButton : OsuButton + public class ReadyButton : TriangleButton { public readonly Bindable SelectedItem = new Bindable(); @@ -32,12 +33,16 @@ namespace osu.Game.Screens.Multi.Match.Components } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { beatmaps.ItemAdded += beatmapAdded; beatmaps.ItemRemoved += beatmapRemoved; SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true); + + BackgroundColour = colours.Green; + Triangles.ColourDark = colours.Green; + Triangles.ColourLight = colours.GreenLight; } private void updateSelectedItem(PlaylistItem item) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a8630b79b1..eef53126c0 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -5,14 +5,11 @@ using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; using osu.Game.Rulesets.Mods; @@ -20,7 +17,6 @@ using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Multi.Play; using osu.Game.Screens.Select; -using osuTK.Graphics; using Footer = osu.Game.Screens.Multi.Match.Components.Footer; namespace osu.Game.Screens.Multi.Match @@ -64,12 +60,6 @@ namespace osu.Game.Screens.Multi.Match { InternalChildren = new Drawable[] { - new HeaderBackgroundSprite - { - RelativeSizeAxes = Axes.X, - Height = 200, - Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.4f), Color4.White.Opacity(0)) - }, new GridContainer { RelativeSizeAxes = Axes.Both, @@ -114,7 +104,7 @@ namespace osu.Game.Screens.Multi.Match { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = new OverlinedParticipants() + Child = new OverlinedParticipants(Direction.Vertical) { RelativeSizeAxes = Axes.Both } }, new Container { @@ -122,6 +112,7 @@ namespace osu.Game.Screens.Multi.Match Padding = new MarginPadding { Horizontal = 5 }, Child = new OverlinedPlaylist(true) // Temporarily always allow selection { + RelativeSizeAxes = Axes.Both, SelectedItem = { BindTarget = SelectedItem } } }, @@ -252,15 +243,5 @@ namespace osu.Game.Screens.Multi.Match if (beatmapManager != null) beatmapManager.ItemAdded -= beatmapAdded; } - - private class HeaderBackgroundSprite : MultiplayerBackgroundSprite - { - protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both }; - - private class BackgroundSprite : UpdateableBeatmapBackgroundSprite - { - protected override double TransformDuration => 200; - } - } } } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 2277157134..1219919425 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -4,24 +4,27 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; 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; -using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Menu; +using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; +using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Play; using osuTK; @@ -62,6 +65,9 @@ namespace osu.Game.Screens.Multi [Resolved(CanBeNull = true)] private OsuLogo logo { get; set; } + private readonly Drawable header; + private readonly Drawable headerBackground; + public Multiplayer() { Anchor = Anchor.Centre; @@ -69,54 +75,65 @@ namespace osu.Game.Screens.Multi RelativeSizeAxes = Axes.Both; Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; + var backgroundColour = OsuColour.FromHex(@"3e3a44"); + InternalChild = waves = new MultiplayerWaveContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Container + new Box { RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"3e3a44"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"3c3842"), - ColourDark = OsuColour.FromHex(@"393540"), - TriangleScale = 5, - }, - }, + Colour = backgroundColour, }, new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = screenStack = new MultiplayerSubScreenStack { RelativeSizeAxes = Axes.Both } + Children = new[] + { + header = new Container + { + 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.7f), backgroundColour) + }, + } + } + }, + screenStack = new MultiplayerSubScreenStack { RelativeSizeAxes = Axes.Both } + } }, new Header(screenStack), - createButton = new HeaderButton + createButton = new CreateRoomButton { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.None, - Size = new Vector2(150, Header.HEIGHT - 20), - Margin = new MarginPadding - { - Top = 10, - Right = 10 + HORIZONTAL_OVERFLOW_PADDING, - }, - Text = "Create room", - Action = () => loungeSubScreen.Open(new Room - { - Name = { Value = $"{api.LocalUser}'s awesome room" } - }), + Action = createRoom }, roomManager = new RoomManager() } @@ -248,6 +265,11 @@ namespace osu.Game.Screens.Multi logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); } + private void createRoom() + { + loungeSubScreen.Open(new Room { Name = { Value = $"{api.LocalUser}'s awesome room" } }); + } + private void beginHandlingTrack() { Beatmap.BindValueChanged(updateTrack, true); @@ -259,7 +281,10 @@ namespace osu.Game.Screens.Multi Beatmap.ValueChanged -= updateTrack; } - private void screenPushed(IScreen lastScreen, IScreen newScreen) => subScreenChanged(newScreen); + private void screenPushed(IScreen lastScreen, IScreen newScreen) + { + subScreenChanged(newScreen); + } private void screenExited(IScreen lastScreen, IScreen newScreen) { @@ -271,6 +296,19 @@ namespace osu.Game.Screens.Multi private void subScreenChanged(IScreen newScreen) { + switch (newScreen) + { + case LoungeSubScreen _: + header.Delay(MultiplayerSubScreen.RESUME_TRANSITION_DELAY).ResizeHeightTo(400, MultiplayerSubScreen.APPEAR_DURATION, Easing.OutQuint); + headerBackground.MoveToX(0, MultiplayerSubScreen.X_MOVE_DURATION, Easing.OutQuint); + break; + + case MatchSubScreen _: + header.ResizeHeightTo(135, MultiplayerSubScreen.APPEAR_DURATION, Easing.OutQuint); + headerBackground.MoveToX(-MultiplayerSubScreen.X_SHIFT, MultiplayerSubScreen.X_MOVE_DURATION, Easing.OutQuint); + break; + } + updatePollingRate(isIdle.Value); createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200); @@ -327,5 +365,36 @@ namespace osu.Game.Screens.Multi FourthWaveColour = OsuColour.FromHex(@"392850"); } } + + private class HeaderBackgroundSprite : MultiplayerBackgroundSprite + { + protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both }; + + private class BackgroundSprite : UpdateableBeatmapBackgroundSprite + { + protected override double TransformDuration => 200; + } + } + + public class CreateRoomButton : PurpleTriangleButton + { + public CreateRoomButton() + { + Size = new Vector2(150, Header.HEIGHT - 20); + Margin = new MarginPadding + { + Top = 10, + Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + Triangles.TriangleScale = 1.5f; + + Text = "Create room"; + } + } } } diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs index ff94f63f01..8e46de1a95 100644 --- a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Multi { @@ -24,31 +23,41 @@ namespace osu.Game.Screens.Multi RelativeSizeAxes = Axes.Both; } + public const float X_SHIFT = 200; + + public const double X_MOVE_DURATION = 800; + + public const double RESUME_TRANSITION_DELAY = DISAPPEAR_DURATION / 2; + + public const double APPEAR_DURATION = 800; + + public const double DISAPPEAR_DURATION = 500; + public override void OnEntering(IScreen last) { - this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - this.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint); + this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(X_SHIFT).MoveToX(0, X_MOVE_DURATION, Easing.OutQuint); } public override bool OnExiting(IScreen next) { - this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - this.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(X_SHIFT, X_MOVE_DURATION, Easing.OutQuint); return false; } public override void OnResuming(IScreen last) { - this.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - this.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.Delay(RESUME_TRANSITION_DELAY).FadeIn(APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(0, X_MOVE_DURATION, Easing.OutQuint); } public override void OnSuspending(IScreen next) { - this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(-X_SHIFT, X_MOVE_DURATION, Easing.OutQuint); } public override string ToString() => Title; diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 997f2382fc..1672131949 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -211,12 +212,24 @@ namespace osu.Game.Screens.Select.Carousel { private readonly BindableBool filtered = new BindableBool(); + private readonly CarouselBeatmap item; + public FilterableDifficultyIcon(CarouselBeatmap item) : base(item.Beatmap) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); filtered.TriggerChange(); + + this.item = item; + } + + protected override bool OnClick(ClickEvent e) + { + if (!filtered.Value) + item.State.Value = CarouselItemState.Selected; + + return true; } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 7ab91677a9..af0d36ea9a 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -51,7 +50,6 @@ namespace osu.Game.Screens.Select.Details { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(4f), Children = new[] { FirstValue = new StatisticRow(), //circle size/key amount @@ -199,6 +197,7 @@ namespace osu.Game.Screens.Select.Details this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Vertical = 2.5f }; Children = new Drawable[] { @@ -206,9 +205,11 @@ namespace osu.Game.Screens.Select.Details { Width = name_width, AutoSizeAxes = Axes.Y, + // osu-web uses 1.25 line-height, which at 12px font size makes the element 14px tall - this compentates that difference + Padding = new MarginPadding { Vertical = 1 }, Child = name = new OsuSpriteText { - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12) }, }, bar = new Bar @@ -239,7 +240,7 @@ namespace osu.Game.Screens.Select.Details { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12) }, }, }; diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index c1e01e3572..cf5e3ba1b3 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -71,31 +71,32 @@ namespace osu.Game.Screens.Select.Details Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "User Rating", - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12), + Margin = new MarginPadding { Bottom = 5 }, }, ratingsBar = new Bar { RelativeSizeAxes = Axes.X, Height = 5, - Margin = new MarginPadding { Top = 5 }, }, new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 10 }, Children = new[] { negativeRatings = new OsuSpriteText { Text = "0", - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12) }, positiveRatings = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Text = @"0", - Font = OsuFont.GetFont(size: 13) + Font = OsuFont.GetFont(size: 12) }, }, }, @@ -104,8 +105,8 @@ namespace osu.Game.Screens.Select.Details Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Rating Spread", - Font = OsuFont.GetFont(size: 13), - Margin = new MarginPadding { Top = 10, Bottom = 5 }, + Font = OsuFont.GetFont(size: 12), + Margin = new MarginPadding { Bottom = 5 }, }, }, }, diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bd5219b872..c034fb6567 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 2c1aff7d3c..7f99338c6e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,7 +73,7 @@ - +