From eedb436389afe01b1666d6e3ed73d10cc8b2d070 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 03:47:42 +0300 Subject: [PATCH 001/670] Move combo counter to ruleset-specific HUD components target --- .../Argon/CatchArgonSkinTransformer.cs | 2 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- .../Argon/TaikoArgonSkinTransformer.cs | 8 +-- osu.Game/Rulesets/Ruleset.cs | 14 ++++- osu.Game/Skinning/ArgonSkin.cs | 16 +----- osu.Game/Skinning/ArgonSkinTransformer.cs | 53 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 1 - osu.Game/Skinning/LegacySkinTransformer.cs | 44 +++++++++++++-- 8 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs index 520c2de248..a67945df98 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs @@ -6,7 +6,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public class CatchArgonSkinTransformer : SkinTransformer + public class CatchArgonSkinTransformer : ArgonSkinTransformer { public CatchArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 0f9c97059c..9526ea05c9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -7,7 +7,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : SkinTransformer + public class OsuArgonSkinTransformer : ArgonSkinTransformer { public OsuArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 9fcecd2b1a..7d38d6c9e5 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -7,16 +7,16 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public class TaikoArgonSkinTransformer : SkinTransformer + public class TaikoArgonSkinTransformer : ArgonSkinTransformer { public TaikoArgonSkinTransformer(ISkin skin) : base(skin) { } - public override Drawable? GetDrawableComponent(ISkinComponentLookup component) + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (component) + switch (lookup) { case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon break; } - return base.GetDrawableComponent(component); + return base.GetDrawableComponent(lookup); } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 37a35fd3ae..c7d4779064 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -212,7 +212,19 @@ namespace osu.Game.Rulesets /// The source skin. /// The current beatmap. /// A skin with a transformer applied, or null if no transformation is provided by this ruleset. - public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null; + public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) + { + switch (skin) + { + case LegacySkin: + return new LegacySkinTransformer(skin); + + case ArgonSkin: + return new ArgonSkinTransformer(skin); + } + + return null; + } protected Ruleset() { diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6fcab6a977..bdb65713a0 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -111,14 +111,13 @@ namespace osu.Game.Skinning return songSelectComponents; case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container => + var mainHUDComponents = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); var healthLine = container.OfType().FirstOrDefault(); var wedgePieces = container.OfType().ToArray(); var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); - var combo = container.OfType().FirstOrDefault(); var songProgress = container.OfType().FirstOrDefault(); var keyCounter = container.OfType().FirstOrDefault(); @@ -192,13 +191,6 @@ namespace osu.Game.Skinning keyCounter.Origin = Anchor.BottomRight; keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); } - - if (combo != null && hitError != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Position = new Vector2((hitError.Width + padding), -(padding * 2 + song_progress_offset_height)); - } } } }) @@ -224,10 +216,6 @@ namespace osu.Game.Skinning CornerRadius = { Value = 0.5f } }, new ArgonAccuracyCounter(), - new ArgonComboCounter - { - Scale = new Vector2(1.3f) - }, new BarHitErrorMeter(), new BarHitErrorMeter(), new ArgonSongProgress(), @@ -235,7 +223,7 @@ namespace osu.Game.Skinning } }; - return skinnableTargetWrapper; + return mainHUDComponents; } return null; diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs new file mode 100644 index 0000000000..387a7a9c0b --- /dev/null +++ b/osu.Game/Skinning/ArgonSkinTransformer.cs @@ -0,0 +1,53 @@ +// 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.Graphics; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Skinning +{ + public class ArgonSkinTransformer : SkinTransformer + { + public ArgonSkinTransformer(ISkin skin) + : base(skin) + { + } + + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Position = new Vector2(36, -66); + combo.Scale = new Vector2(1.3f); + } + }) + { + new ArgonComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + } + + return base.GetDrawableComponent(lookup); + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f0cd59b68..b8e721165e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -399,7 +399,6 @@ namespace osu.Game.Skinning { Children = new Drawable[] { - new LegacyComboCounter(), new LegacyScoreCounter(), new LegacyAccuracyCounter(), new LegacySongProgress(), diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 367e5bae01..3ea316c0c7 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,28 +1,62 @@ // 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.Audio.Sample; +using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; +using osuTK; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { - /// - /// Transformer used to handle support of legacy features for individual rulesets. - /// - public abstract class LegacySkinTransformer : SkinTransformer + public class LegacySkinTransformer : SkinTransformer { /// /// Whether the skin being transformed is able to provide legacy resources for the ruleset. /// public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo); - protected LegacySkinTransformer(ISkin skin) + public LegacySkinTransformer(ISkin skin) : base(skin) { } + public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) + { + switch (lookup) + { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + var rulesetHUDComponents = base.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + new LegacyComboCounter() + }; + + return rulesetHUDComponents; + } + + break; + } + + return base.GetDrawableComponent(lookup); + } + public override ISample? GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) From e469e06271e4e4b23a93b5d0c30bf7693fb947e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 03:54:53 +0300 Subject: [PATCH 002/670] Refactor `CatchLegacySkinTransformer` logic and remove `HiddenByRulesetImplementation` entirely --- .../TestSceneCatchPlayerLegacySkin.cs | 8 +- .../Legacy/CatchLegacySkinTransformer.cs | 102 ++++++++---------- .../TestSceneSkinnableComboCounter.cs | 13 --- osu.Game/Skinning/LegacyComboCounter.cs | 12 --- 4 files changed, 49 insertions(+), 86 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 5406230359..99325e14c8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Skinning; @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); [Test] - public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin) + public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin) { if (withModifiedSkin) { @@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests CreateTest(); } - AddAssert("legacy HUD combo counter hidden", () => - { - return Player.ChildrenOfType().All(c => c.ChildrenOfType().Single().Alpha == 0f); - }); + AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); } } } diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index fb8af9bdb6..675c61a2c5 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -1,10 +1,9 @@ // 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 System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osuTK.Graphics; @@ -28,76 +27,69 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup containerLookup) + switch (lookup) { - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - var components = base.GetDrawableComponent(lookup) as Container; + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME); + // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. + return Skin.GetDrawableComponent(lookup); + } - if (providesComboCounter && components != null) - { - // catch may provide its own combo counter; hide the default. - // todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed. - foreach (var legacyComboCounter in components.OfType()) - legacyComboCounter.HiddenByRulesetImplementation = false; - } + break; - return components; - } - } + case CatchSkinComponentLookup catchSkinComponent: + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.Fruit: + if (hasPear) + return new LegacyFruitPiece(); - if (lookup is CatchSkinComponentLookup catchSkinComponent) - { - switch (catchSkinComponent.Component) - { - case CatchSkinComponents.Fruit: - if (hasPear) - return new LegacyFruitPiece(); + return null; - return null; + case CatchSkinComponents.Banana: + if (GetTexture("fruit-bananas") != null) + return new LegacyBananaPiece(); - case CatchSkinComponents.Banana: - if (GetTexture("fruit-bananas") != null) - return new LegacyBananaPiece(); + return null; - return null; + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyDropletPiece(); - case CatchSkinComponents.Droplet: - if (GetTexture("fruit-drop") != null) - return new LegacyDropletPiece(); + return null; - return null; + case CatchSkinComponents.Catcher: + decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; - case CatchSkinComponents.Catcher: - decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; + if (version < 2.3m) + { + if (hasOldStyleCatcherSprite()) + return new LegacyCatcherOld(); + } - if (version < 2.3m) - { - if (hasOldStyleCatcherSprite()) - return new LegacyCatcherOld(); - } + if (hasNewStyleCatcherSprite()) + return new LegacyCatcherNew(); - if (hasNewStyleCatcherSprite()) - return new LegacyCatcherNew(); + return null; - return null; + case CatchSkinComponents.CatchComboCounter: + if (providesComboCounter) + return new LegacyCatchComboCounter(); - case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) - return new LegacyCatchComboCounter(); + return null; - return null; + case CatchSkinComponents.HitExplosion: + if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) + return new LegacyHitExplosion(); - case CatchSkinComponents.HitExplosion: - if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) - return new LegacyHitExplosion(); + return null; - return null; - - default: - throw new UnsupportedSkinComponentException(lookup); - } + default: + throw new UnsupportedSkinComponentException(lookup); + } } return base.GetDrawableComponent(lookup); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index 72f40d9c6f..a15a3197c5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -4,7 +4,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reset combo", () => scoreProcessor.Combo.Value = 0); } - - [Test] - public void TestLegacyComboCounterHiddenByRulesetImplementation() - { - AddToggleStep("toggle legacy hidden by ruleset", visible => - { - foreach (var legacyCounter in this.ChildrenOfType()) - legacyCounter.HiddenByRulesetImplementation = visible; - }); - - AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10); - } } } diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index cd72055fce..d77a39f607 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -43,18 +43,6 @@ namespace osu.Game.Skinning private readonly Container counterContainer; - /// - /// Hides the combo counter internally without affecting its . - /// - /// - /// This is used for rulesets that provide their own combo counter and don't want this HUD one to be visible, - /// without potentially affecting the user's selected skin. - /// - public bool HiddenByRulesetImplementation - { - set => counterContainer.Alpha = value ? 1 : 0; - } - public bool UsesFixedAnchor { get; set; } public LegacyComboCounter() From 78cb6b68518651a568c00c3434b9695ec76f6c53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:29:18 +0300 Subject: [PATCH 003/670] Abstractify `LegacyComboCounter` to re-use for mania --- .../TestSceneCatchPlayerLegacySkin.cs | 2 +- .../TestSceneSkinnableComboCounter.cs | 2 +- osu.Game/Skinning/LegacyComboCounter.cs | 163 +++++------------- .../Skinning/LegacyDefaultComboCounter.cs | 85 +++++++++ osu.Game/Skinning/LegacySkinTransformer.cs | 4 +- osu.Game/Skinning/Skin.cs | 1 + 6 files changed, 134 insertions(+), 123 deletions(-) create mode 100644 osu.Game/Skinning/LegacyDefaultComboCounter.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 99325e14c8..7812e02a63 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests CreateTest(); } - AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); + AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType().Any()); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index a15a3197c5..a6196a8ca0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Drawable CreateArgonImplementation() => new ArgonComboCounter(); protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter(); - protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter(); + protected override Drawable CreateLegacyImplementation() => new LegacyDefaultComboCounter(); [Test] public void TestComboCounterIncrementing() diff --git a/osu.Game/Skinning/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs index d77a39f607..7003e0d3c8 100644 --- a/osu.Game/Skinning/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -5,25 +5,17 @@ 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.Scoring; -using osuTK; namespace osu.Game.Skinning { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. /// - public partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable + public abstract partial class LegacyComboCounter : CompositeDrawable, ISerialisableDrawable { public Bindable Current { get; } = new BindableInt { MinValue = 0 }; - private uint scheduledPopOutCurrentId; - - private const double big_pop_out_duration = 300; - - private const double small_pop_out_duration = 100; - private const double fade_out_duration = 100; /// @@ -31,9 +23,8 @@ namespace osu.Game.Skinning /// private const double rolling_duration = 20; - private readonly Drawable popOutCount; - - private readonly Drawable displayedCountSpriteText; + protected readonly LegacySpriteText PopOutCountText; + protected readonly LegacySpriteText DisplayedCountText; private int previousValue; @@ -45,17 +36,10 @@ namespace osu.Game.Skinning public bool UsesFixedAnchor { get; set; } - public LegacyComboCounter() + protected LegacyComboCounter() { AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - Margin = new MarginPadding(10); - - Scale = new Vector2(1.28f); - InternalChildren = new[] { counterContainer = new Container @@ -63,18 +47,16 @@ namespace osu.Game.Skinning AlwaysPresent = true, Children = new[] { - popOutCount = new LegacySpriteText(LegacyFont.Combo) + PopOutCountText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, Blending = BlendingParameters.Additive, - Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, - displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) + DisplayedCountText = new LegacySpriteText(LegacyFont.Combo) { Alpha = 0, AlwaysPresent = true, - Anchor = Anchor.BottomLeft, BypassAutoSizeAxes = Axes.Both, }, } @@ -114,26 +96,12 @@ namespace osu.Game.Skinning { base.LoadComplete(); - ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); - ((IHasText)popOutCount).Text = formatCount(Current.Value); + DisplayedCountText.Text = FormatCount(Current.Value); + PopOutCountText.Text = FormatCount(Current.Value); Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); - updateLayout(); - } - - private void updateLayout() - { - const float font_height_ratio = 0.625f; - const float vertical_offset = 9; - - displayedCountSpriteText.OriginPosition = new Vector2(0, font_height_ratio * displayedCountSpriteText.Height + vertical_offset); - displayedCountSpriteText.Position = new Vector2(0, -(1 - font_height_ratio) * displayedCountSpriteText.Height + vertical_offset); - - popOutCount.OriginPosition = new Vector2(3, font_height_ratio * popOutCount.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left - popOutCount.Position = new Vector2(0, -(1 - font_height_ratio) * popOutCount.Height + vertical_offset); - - counterContainer.Size = displayedCountSpriteText.Size; + counterContainer.Size = DisplayedCountText.Size; } private void updateCount(bool rolling) @@ -147,127 +115,84 @@ namespace osu.Game.Skinning if (!rolling) { FinishTransforms(false, nameof(DisplayedCount)); + isRolling = false; DisplayedCount = prev; if (prev + 1 == Current.Value) - onCountIncrement(prev, Current.Value); + OnCountIncrement(); else - onCountChange(Current.Value); + OnCountChange(); } else { - onCountRolling(displayedCount, Current.Value); + OnCountRolling(); isRolling = true; } } - private void transformPopOut(int newValue) + /// + /// Raised when the counter should display the new value with transitions. + /// + protected virtual void OnCountIncrement() { - ((IHasText)popOutCount).Text = formatCount(newValue); - - popOutCount.ScaleTo(1.56f) - .ScaleTo(1, big_pop_out_duration); - - popOutCount.FadeTo(0.6f) - .FadeOut(big_pop_out_duration); - } - - private void transformNoPopOut(int newValue) - { - ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); - - counterContainer.Size = displayedCountSpriteText.Size; - - displayedCountSpriteText.ScaleTo(1); - } - - private void transformPopOutSmall(int newValue) - { - ((IHasText)displayedCountSpriteText).Text = formatCount(newValue); - - counterContainer.Size = displayedCountSpriteText.Size; - - displayedCountSpriteText.ScaleTo(1).Then() - .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() - .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); - } - - private void scheduledPopOutSmall(uint id) - { - // Too late; scheduled task invalidated - if (id != scheduledPopOutCurrentId) - return; + if (DisplayedCount < Current.Value - 1) + DisplayedCount++; DisplayedCount++; } - private void onCountIncrement(int currentValue, int newValue) + /// + /// Raised when the counter should roll to the new combo value (usually roll back to zero). + /// + protected virtual void OnCountRolling() { - scheduledPopOutCurrentId++; - - if (DisplayedCount < currentValue) - DisplayedCount++; - - displayedCountSpriteText.Show(); - - transformPopOut(newValue); - - uint newTaskId = scheduledPopOutCurrentId; - - Scheduler.AddDelayed(delegate - { - scheduledPopOutSmall(newTaskId); - }, big_pop_out_duration - 140); - } - - private void onCountRolling(int currentValue, int newValue) - { - scheduledPopOutCurrentId++; - // Hides displayed count if was increasing from 0 to 1 but didn't finish - if (currentValue == 0 && newValue == 0) - displayedCountSpriteText.FadeOut(fade_out_duration); + if (DisplayedCount == 0 && Current.Value == 0) + DisplayedCountText.FadeOut(fade_out_duration); - transformRoll(currentValue, newValue); + transformRoll(DisplayedCount, Current.Value); } - private void onCountChange(int newValue) + /// + /// Raised when the counter should display the new combo value without any transitions. + /// + protected virtual void OnCountChange() { - scheduledPopOutCurrentId++; + if (Current.Value == 0) + DisplayedCountText.FadeOut(); - if (newValue == 0) - displayedCountSpriteText.FadeOut(); - - DisplayedCount = newValue; + DisplayedCount = Current.Value; } private void onDisplayedCountRolling(int newValue) { if (newValue == 0) - displayedCountSpriteText.FadeOut(fade_out_duration); - else - displayedCountSpriteText.Show(); + DisplayedCountText.FadeOut(fade_out_duration); - transformNoPopOut(newValue); + DisplayedCountText.Text = FormatCount(newValue); + counterContainer.Size = DisplayedCountText.Size; } private void onDisplayedCountChange(int newValue) { - displayedCountSpriteText.FadeTo(newValue == 0 ? 0 : 1); - transformNoPopOut(newValue); + DisplayedCountText.FadeTo(newValue == 0 ? 0 : 1); + DisplayedCountText.Text = FormatCount(newValue); + + counterContainer.Size = DisplayedCountText.Size; } private void onDisplayedCountIncrement(int newValue) { - displayedCountSpriteText.Show(); - transformPopOutSmall(newValue); + DisplayedCountText.Text = FormatCount(newValue); + + counterContainer.Size = DisplayedCountText.Size; } private void transformRoll(int currentValue, int newValue) => this.TransformTo(nameof(DisplayedCount), newValue, getProportionalDuration(currentValue, newValue)); - private string formatCount(int count) => $@"{count}x"; + protected virtual string FormatCount(int count) => $@"{count}"; private double getProportionalDuration(int currentValue, int newValue) { diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs new file mode 100644 index 0000000000..f633358993 --- /dev/null +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Threading; +using osuTK; + +namespace osu.Game.Skinning +{ + /// + /// Uses the 'x' symbol and has a pop-out effect while rolling over. + /// + public partial class LegacyDefaultComboCounter : LegacyComboCounter + { + private const double big_pop_out_duration = 300; + private const double small_pop_out_duration = 100; + + private ScheduledDelegate? scheduledPopOut; + + public LegacyDefaultComboCounter() + { + Margin = new MarginPadding(10); + + PopOutCountText.Anchor = Anchor.BottomLeft; + DisplayedCountText.Anchor = Anchor.BottomLeft; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + const float font_height_ratio = 0.625f; + const float vertical_offset = 9; + + DisplayedCountText.OriginPosition = new Vector2(0, font_height_ratio * DisplayedCountText.Height + vertical_offset); + DisplayedCountText.Position = new Vector2(0, -(1 - font_height_ratio) * DisplayedCountText.Height + vertical_offset); + + PopOutCountText.OriginPosition = new Vector2(3, font_height_ratio * PopOutCountText.Height + vertical_offset); // In stable, the bigger pop out scales a bit to the left + PopOutCountText.Position = new Vector2(0, -(1 - font_height_ratio) * PopOutCountText.Height + vertical_offset); + } + + protected override void OnCountIncrement() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + DisplayedCountText.Show(); + + PopOutCountText.Text = FormatCount(Current.Value); + + PopOutCountText.ScaleTo(1.56f) + .ScaleTo(1, big_pop_out_duration); + + PopOutCountText.FadeTo(0.6f) + .FadeOut(big_pop_out_duration); + + this.Delay(big_pop_out_duration - 140).Schedule(() => + { + base.OnCountIncrement(); + + DisplayedCountText.ScaleTo(1).Then() + .ScaleTo(1.1f, small_pop_out_duration / 2, Easing.In).Then() + .ScaleTo(1, small_pop_out_duration / 2, Easing.Out); + }, out scheduledPopOut); + } + + protected override void OnCountRolling() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + base.OnCountRolling(); + } + + protected override void OnCountChange() + { + scheduledPopOut?.Cancel(); + scheduledPopOut = null; + + base.OnCountChange(); + } + + protected override string FormatCount(int count) => $@"{count}x"; + } +} diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 3ea316c0c7..66978fc6b0 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Skinning rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => { - var combo = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); if (combo != null) { @@ -45,7 +45,7 @@ namespace osu.Game.Skinning } }) { - new LegacyComboCounter() + new LegacyDefaultComboCounter() }; return rulesetHUDComponents; diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 9ee69d033d..80bb340109 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -153,6 +153,7 @@ namespace osu.Game.Skinning // handle namespace changes... jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); + jsonContent = jsonContent.Replace(@"osu.Game.Skinning.LegacyComboCounter", @"osu.Game.Skinning.LegacyDefaultComboCounter"); var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); From ece532b837a3ebe76791e8fc5c528cba854a5ad0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:18:56 +0300 Subject: [PATCH 004/670] Add legacy mania combo counter lookups --- osu.Game/Skinning/LegacyManiaSkinConfiguration.cs | 1 + osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs | 2 ++ osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 4 ++++ osu.Game/Skinning/LegacySkin.cs | 6 ++++++ 4 files changed, 13 insertions(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 042836984a..db1f216b6e 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -39,6 +39,7 @@ namespace osu.Game.Skinning public float HitPosition = DEFAULT_HIT_POSITION; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; + public float ComboPosition = 111 * POSITION_SCALE_FACTOR; public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; public bool KeysUnderNotes; diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index cacca0de23..fc90fc89eb 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -42,6 +42,7 @@ namespace osu.Game.Skinning LeftLineWidth, RightLineWidth, HitPosition, + ComboPosition, ScorePosition, LightPosition, StagePaddingTop, @@ -63,6 +64,7 @@ namespace osu.Game.Skinning JudgementLineColour, ColumnBackgroundColour, ColumnLightColour, + ComboBreakColour, MinimumColumnWidth, LeftStageImage, RightStageImage, diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index b472afb74f..5dd8f9c52d 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -94,6 +94,10 @@ namespace osu.Game.Skinning currentConfig.LightPosition = (480 - float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; + case "ComboPosition": + currentConfig.ComboPosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; + break; + case "ScorePosition": currentConfig.ScorePosition = (float.Parse(pair.Value, CultureInfo.InvariantCulture)) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR; break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b8e721165e..848a6366ed 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -152,6 +152,9 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.HitPosition: return SkinUtils.As(new Bindable(existing.HitPosition)); + case LegacyManiaSkinConfigurationLookups.ComboPosition: + return SkinUtils.As(new Bindable(existing.ComboPosition)); + case LegacyManiaSkinConfigurationLookups.ScorePosition: return SkinUtils.As(new Bindable(existing.ScorePosition)); @@ -189,6 +192,9 @@ namespace osu.Game.Skinning Debug.Assert(maniaLookup.ColumnIndex != null); return SkinUtils.As(getCustomColour(existing, $"ColourLight{maniaLookup.ColumnIndex + 1}")); + case LegacyManiaSkinConfigurationLookups.ComboBreakColour: + return SkinUtils.As(getCustomColour(existing, "ColourBreak")); + case LegacyManiaSkinConfigurationLookups.MinimumColumnWidth: return SkinUtils.As(new Bindable(existing.MinimumColumnWidth)); From 8be3f4f632f62d70c365c45c1f48a1747b4579f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:20:44 +0300 Subject: [PATCH 005/670] Add legacy mania combo counter implementation --- .../Legacy/LegacyManiaComboCounter.cs | 94 +++++++++++++++++++ .../Legacy/ManiaLegacySkinTransformer.cs | 31 ++++++ 2 files changed, 125 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs new file mode 100644 index 0000000000..fd309f6250 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Legacy +{ + public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable + { + private DrawableManiaRuleset maniaRuleset = null!; + + bool ISerialisableDrawable.SupportsClosestAnchor => false; + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableRuleset ruleset) + { + maniaRuleset = (DrawableManiaRuleset)ruleset; + + DisplayedCountText.Anchor = Anchor.Centre; + DisplayedCountText.Origin = Anchor.Centre; + + PopOutCountText.Anchor = Anchor.Centre; + PopOutCountText.Origin = Anchor.Centre; + PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; + + UsesFixedAnchor = true; + } + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + + protected override void OnCountIncrement() + { + base.OnCountIncrement(); + + PopOutCountText.Hide(); + DisplayedCountText.ScaleTo(new Vector2(1f, 1.4f)) + .ScaleTo(new Vector2(1f), 300, Easing.Out) + .FadeIn(120); + } + + protected override void OnCountChange() + { + base.OnCountChange(); + + PopOutCountText.Hide(); + DisplayedCountText.ScaleTo(1f); + } + + protected override void OnCountRolling() + { + if (DisplayedCount > 0) + { + PopOutCountText.Text = FormatCount(DisplayedCount); + PopOutCountText.FadeTo(0.8f).FadeOut(200) + .ScaleTo(1f).ScaleTo(4f, 200); + + DisplayedCountText.FadeTo(0.5f, 300); + } + + base.OnCountRolling(); + } + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 73c521b2ed..c539c239bd 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -5,9 +5,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; @@ -78,6 +81,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { switch (lookup) { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); From 408287e086b18187c72e0bde57571be39fd621b0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:20:51 +0300 Subject: [PATCH 006/670] Add very basic argon mania combo counter implementation --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 84 +++++++++++++++++++ .../Argon/ManiaArgonSkinTransformer.cs | 33 +++++++- 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs new file mode 100644 index 0000000000..1c8e43345a --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -0,0 +1,84 @@ +// 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.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Skinning.Argon +{ + public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable + { + private OsuSpriteText text = null!; + + protected override double RollingDuration => 500; + protected override Easing RollingEasing => Easing.OutQuint; + + private DrawableManiaRuleset maniaRuleset = null!; + + bool ISerialisableDrawable.SupportsClosestAnchor => false; + + [BackgroundDependencyLoader] + private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor) + { + maniaRuleset = (DrawableManiaRuleset)ruleset; + + Current.BindTo(scoreProcessor.Combo); + Current.BindValueChanged(combo => + { + if (combo.OldValue == 0 && combo.NewValue > 0) + text.FadeIn(200, Easing.OutQuint); + else if (combo.OldValue > 0 && combo.NewValue == 0) + { + if (combo.OldValue > 1) + text.FlashColour(Color4.Red, 2000, Easing.OutQuint); + + text.FadeOut(200, Easing.InQuint); + } + }); + + UsesFixedAnchor = true; + } + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + text.Alpha = Current.Value > 0 ? 1 : 0; + + direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + + protected override IHasText CreateText() => text = new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 32, fixedWidth: true), + }; + } +} diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 7f6540e7b5..b0a6086f2a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,8 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Scoring; @@ -12,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public class ManiaArgonSkinTransformer : SkinTransformer + public class ManiaArgonSkinTransformer : ArgonSkinTransformer { private readonly ManiaBeatmap beatmap; @@ -26,6 +29,34 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { switch (lookup) { + case SkinComponentsContainerLookup containerLookup: + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: + Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + + var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); + + rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; + + return rulesetHUDComponents; + } + + break; + case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) From 95961dc98955aed19f1e5fd482ba119be868ec0b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:21:10 +0300 Subject: [PATCH 007/670] Add various visual test coverage --- .../Skinning/TestSceneComboCounter.cs | 38 +++++++++++++++++++ .../Skinning/TestScenePlayfield.cs | 13 +++++++ .../TestSceneManiaPlayerLegacySkin.cs | 36 ++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs new file mode 100644 index 0000000000..c1e1cfd7af --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneComboCounter.cs @@ -0,0 +1,38 @@ +// 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.Testing; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Skinning.Argon; +using osu.Game.Rulesets.Mania.Skinning.Legacy; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Tests.Skinning +{ + public partial class TestSceneComboCounter : ManiaSkinnableTestScene + { + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset()); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("setup", () => SetContents(s => + { + if (s is ArgonSkin) + return new ArgonManiaComboCounter(); + + if (s is LegacySkin) + return new LegacyManiaComboCounter(); + + return new LegacyManiaComboCounter(); + })); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Great }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs index 29c47ca93a..110336d823 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestScenePlayfield.cs @@ -3,15 +3,22 @@ using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Mania.Tests.Skinning { public partial class TestScenePlayfield : ManiaSkinnableTestScene { + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(new ManiaRuleset()); + private List stageDefinitions = new List(); [Test] @@ -29,6 +36,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning Child = new ManiaPlayfield(stageDefinitions) }); }); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); } [TestCase(2)] @@ -54,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning } }); }); + + AddRepeatStep("perform hit", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Perfect }), 20); + AddStep("perform miss", () => scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new Judgement()) { Type = HitResult.Miss })); } protected override IBeatmap CreateBeatmapForSkinProvider() diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs new file mode 100644 index 0000000000..0f10f96dbf --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayerLegacySkin.cs @@ -0,0 +1,36 @@ +// 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.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public partial class TestSceneManiaPlayerLegacySkin : LegacySkinPlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + // play with a converted beatmap to allow dual stages mod to work. + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(new RulesetInfo()); + + protected override bool HasCustomSteps => true; + + [Test] + public void TestSingleStage() + { + AddStep("Load single stage", LoadPlayer); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + } + + [Test] + public void TestDualStage() + { + AddStep("Load dual stage", () => LoadPlayer(new Mod[] { new ManiaModDualStages() })); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + } + } +} From 01219fa3712f46ea55c0df150c2c3be2c0a31a48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 05:16:41 +0300 Subject: [PATCH 008/670] Disable "closest" anchor in mania combo counter for convenience --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 ++- osu.Game/Skinning/ISerialisableDrawable.cs | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index f972186333..cc1e5b26ec 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -441,6 +441,9 @@ namespace osu.Game.Overlays.SkinEditor drawableComponent.Origin = Anchor.TopCentre; drawableComponent.Anchor = Anchor.TopCentre; drawableComponent.Y = targetContainer.DrawSize.Y / 2; + + if (!component.SupportsClosestAnchor) + component.UsesFixedAnchor = true; } try diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index cf6fb60636..208bd71005 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -233,7 +233,8 @@ namespace osu.Game.Overlays.SkinEditor { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { - State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } + State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), }, + Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) }, }; yield return new OsuMenuItem("Anchor") diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index c9dcaca6d1..898186bcc1 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -27,6 +27,14 @@ namespace osu.Game.Skinning /// bool IsEditable => true; + /// + /// Whether this component supports the "closest" anchor. + /// + /// + /// This is disabled by some components that shift position automatically. + /// + bool SupportsClosestAnchor => true; + /// /// In the context of the skin layout editor, whether this has a permanent anchor defined. /// If , this 's is automatically determined by proximity, From 02f5ea200ea7b464d6d78170dc7b7beeb3e61559 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Dec 2023 07:41:55 +0300 Subject: [PATCH 009/670] Fix failing tests --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 13 +++++------ .../Legacy/LegacyManiaComboCounter.cs | 22 ++++++++++--------- osu.Game/Screens/Play/Player.cs | 4 ++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 1c8e43345a..ad515528fb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -7,9 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -24,15 +22,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; - private DrawableManiaRuleset maniaRuleset = null!; - bool ISerialisableDrawable.SupportsClosestAnchor => false; [BackgroundDependencyLoader] - private void load(DrawableRuleset ruleset, ScoreProcessor scoreProcessor) + private void load(ScoreProcessor scoreProcessor) { - maniaRuleset = (DrawableManiaRuleset)ruleset; - Current.BindTo(scoreProcessor.Combo); Current.BindValueChanged(combo => { @@ -50,6 +44,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon UsesFixedAnchor = true; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + private IBindable direction = null!; protected override void LoadComplete() @@ -57,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon base.LoadComplete(); text.Alpha = Current.Value > 0 ? 1 : 0; - direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); // two schedules are required so that updateAnchor is executed in the next frame, diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index fd309f6250..00619834c8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -3,9 +3,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; @@ -15,15 +14,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable { - private DrawableManiaRuleset maniaRuleset = null!; - bool ISerialisableDrawable.SupportsClosestAnchor => false; [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableRuleset ruleset) + private void load(ISkinSource skin) { - maniaRuleset = (DrawableManiaRuleset)ruleset; - DisplayedCountText.Anchor = Anchor.Centre; DisplayedCountText.Origin = Anchor.Centre; @@ -34,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy UsesFixedAnchor = true; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + private IBindable direction = null!; protected override void LoadComplete() { base.LoadComplete(); - direction = maniaRuleset.ScrollingInfo.Direction.GetBoundCopy(); + direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); // two schedules are required so that updateAnchor is executed in the next frame, @@ -50,8 +48,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void updateAnchor() { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlagFast(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } // since we flip the vertical anchor when changing scroll direction, // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c960ac357f..c10ef9731f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -32,6 +32,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play.HUD; @@ -224,6 +225,9 @@ namespace osu.Game.Screens.Play DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods); dependencies.CacheAs(DrawableRuleset); + if (DrawableRuleset is IDrawableScrollingRuleset scrollingRuleset) + dependencies.CacheAs(scrollingRuleset.ScrollingInfo); + ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.Mods.Value = gameplayMods; ScoreProcessor.ApplyBeatmap(playableBeatmap); From 225b309ba357b177a6259a447afea8e18e261ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 20 Jun 2024 16:27:07 +0200 Subject: [PATCH 010/670] Reimplement stable polygon tool Addresses https://github.com/ppy/osu/discussions/19970. While yes, https://github.com/ppy/osu/pull/26303 is also a thing, in discussing with users I don't think that grids are going to be able to deprecate this feature. Logic transcribed verbatim from stable. --- .../Edit/GenerateToolboxGroup.cs | 54 +++++ .../Edit/OsuHitObjectComposer.cs | 3 +- .../Edit/PolygonGenerationPopover.cs | 193 ++++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs diff --git a/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs new file mode 100644 index 0000000000..4e188a2b86 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class GenerateToolboxGroup : EditorToolboxGroup + { + private readonly EditorToolButton polygonButton; + + public GenerateToolboxGroup() + : base("Generate") + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(5), + Children = new Drawable[] + { + polygonButton = new EditorToolButton("Polygon", + () => new SpriteIcon { Icon = FontAwesome.Solid.Spinner }, + () => new PolygonGenerationPopover()), + } + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat) return false; + + switch (e.Key) + { + case Key.D: + if (!e.ControlPressed || !e.ShiftPressed) + return false; + + polygonButton.TriggerClick(); + return true; + + default: + return false; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 41f6b41f82..fab5298554 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable placementObject; [Cached(typeof(IDistanceSnapProvider))] - protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); @@ -109,6 +109,7 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, + new GenerateToolboxGroup(), FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs new file mode 100644 index 0000000000..6325de5851 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class PolygonGenerationPopover : OsuPopover + { + private SliderWithTextBoxInput distanceSnapInput = null!; + private SliderWithTextBoxInput offsetAngleInput = null!; + private SliderWithTextBoxInput repeatCountInput = null!; + private SliderWithTextBoxInput pointInput = null!; + private RoundedButton commitButton = null!; + + private readonly List insertedCircles = new List(); + private bool began; + private bool committed; + + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } = null!; + + [Resolved] + private EditorClock editorClock { get; set; } = null!; + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private HitObjectComposer composer { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + Width = 220, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Children = new Drawable[] + { + distanceSnapInput = new SliderWithTextBoxInput("Distance snap:") + { + Current = new BindableNumber(1) + { + MinValue = 0.1, + MaxValue = 6, + Precision = 0.1, + Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value, + }, + Instantaneous = true + }, + offsetAngleInput = new SliderWithTextBoxInput("Offset angle:") + { + Current = new BindableNumber + { + MinValue = 0, + MaxValue = 180, + Precision = 1 + }, + Instantaneous = true + }, + repeatCountInput = new SliderWithTextBoxInput("Repeats:") + { + Current = new BindableNumber(1) + { + MinValue = 1, + MaxValue = 10, + Precision = 1 + }, + Instantaneous = true + }, + pointInput = new SliderWithTextBoxInput("Vertices:") + { + Current = new BindableNumber(3) + { + MinValue = 3, + MaxValue = 10, + Precision = 1, + }, + Instantaneous = true + }, + commitButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Create", + Action = commit + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + changeHandler?.BeginChange(); + began = true; + + distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon()); + offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon()); + repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon()); + pointInput.Current.BindValueChanged(_ => tryCreatePolygon()); + tryCreatePolygon(); + } + + private void tryCreatePolygon() + { + double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime); + TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime); + double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor; + IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType().LastOrDefault() ?? new Slider(); + double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier + / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME); + double length = distanceSnapInput.Current.Value * velocity * timeSpacing; + float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value))); + + editorBeatmap.RemoveRange(insertedCircles); + insertedCircles.Clear(); + + var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler; + bool first = true; + + for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i) + { + float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value); + var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle)); + + var circle = new HitCircle + { + Position = position, + StartTime = startTime, + NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True, + }; + // TODO: probably ensure samples also follow current ternary status (not trivial) + circle.Samples.Add(circle.CreateHitSampleInfo()); + + if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y) + { + commitButton.Enabled.Value = false; + return; + } + + insertedCircles.Add(circle); + startTime = beatSnapProvider.SnapTime(startTime + timeSpacing); + + first = false; + } + + editorBeatmap.AddRange(insertedCircles); + commitButton.Enabled.Value = true; + } + + private void commit() + { + changeHandler?.EndChange(); + committed = true; + Hide(); + } + + protected override void PopOut() + { + base.PopOut(); + + if (began && !committed) + { + editorBeatmap.RemoveRange(insertedCircles); + changeHandler?.EndChange(); + } + } + } +} From a56751511e34d67b1eaf4ebebb2566f6184d94fb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 22 Jun 2024 01:36:30 +0900 Subject: [PATCH 011/670] Apply a ducking effect to the currently playing track when switching ruleset --- osu.Game/Overlays/MusicController.cs | 57 +++++++++++++++++++ .../Toolbar/ToolbarRulesetSelector.cs | 31 +++++++++- .../Toolbar/ToolbarRulesetTabButton.cs | 12 ---- 3 files changed, 87 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0986c0513c..678ae92d4b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Threading; +using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets.Mods; @@ -63,6 +64,16 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } + private AudioFilter audioDuckFilter; + private readonly BindableDouble audioDuckVolume = new BindableDouble(1); + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -243,6 +254,52 @@ namespace osu.Game.Overlays onSuccess?.Invoke(); }); + /// + /// Attenuates the volume and/or filters the currently playing track. + /// + /// Duration of the ducking transition, in ms. + /// Level to drop volume to (1.0 = 100%). + /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Easing for the ducking transition. + public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, Easing easing = Easing.InCubic) + { + Schedule(() => + { + audioDuckFilter?.CutoffTo(duckCutoffTo, duration, easing); + this.TransformBindableTo(audioDuckVolume, duckVolumeTo, duration, easing); + }); + } + + /// + /// Restores the volume to full and stops filtering the currently playing track after having used . + /// + /// Duration of the unducking transition, in ms. + /// Easing for the unducking transition. + public void Unduck(int duration = 500, Easing easing = Easing.InCubic) + { + Schedule(() => + { + audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, duration, easing); + this.TransformBindableTo(audioDuckVolume, 1, duration, easing); + }); + } + + /// + /// A convenience method that ducks the currently playing track, then after a delay, unducks it. + /// + /// Delay after audio is ducked before unducking begins, in ms. + /// Duration of the unducking transition, in ms. + /// Easing for the unducking transition. + /// Level to drop volume to (1.0 = 100%). + /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Duration of the ducking transition, in ms. + /// Easing for the ducking transition. + public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.InCubic) + { + Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); + Scheduler.AddDelayed(() => Unduck(unduckDuration, unduckEasing), delay); + } + private bool next() { if (beatmap.Disabled || !AllowTrackControl.Value) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index d49c340ed4..93be108b71 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -3,8 +3,12 @@ #nullable disable +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -21,6 +25,12 @@ namespace osu.Game.Overlays.Toolbar { protected Drawable ModeButtonLine { get; private set; } + [Resolved] + private MusicController musicController { get; set; } + + private readonly Dictionary rulesetSelectionSample = new Dictionary(); + private readonly Dictionary rulesetSelectionChannel = new Dictionary(); + public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; @@ -28,7 +38,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { AddRangeInternal(new[] { @@ -54,6 +64,11 @@ namespace osu.Game.Overlays.Toolbar } }, }); + + foreach (var r in Rulesets.AvailableRulesets) + rulesetSelectionSample[r] = audio.Samples.Get($@"UI/ruleset-select-{r.ShortName}"); + + Current.ValueChanged += playRulesetSelectionSample; } protected override void LoadComplete() @@ -84,6 +99,20 @@ namespace osu.Game.Overlays.Toolbar } } + private void playRulesetSelectionSample(ValueChangedEvent r) + { + if (r.OldValue == null) + return; + + if (rulesetSelectionChannel.TryGetValue(r.OldValue, out var sampleChannel)) + sampleChannel?.Stop(); + + rulesetSelectionChannel[r.NewValue] = rulesetSelectionSample[r.NewValue].GetChannel(); + rulesetSelectionChannel[r.NewValue]?.Play(); + + musicController?.TimedDuck(600); + } + public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 0315bede64..3287ac6eaa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -19,8 +17,6 @@ namespace osu.Game.Overlays.Toolbar { private readonly RulesetButton ruleset; - private Sample? selectSample; - public ToolbarRulesetTabButton(RulesetInfo value) : base(value) { @@ -38,18 +34,10 @@ namespace osu.Game.Overlays.Toolbar ruleset.SetIcon(rInstance.CreateIcon()); } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - selectSample = audio.Samples.Get($@"UI/ruleset-select-{Value.ShortName}"); - } - protected override void OnActivated() => ruleset.Active = true; protected override void OnDeactivated() => ruleset.Active = false; - protected override void OnActivatedByUser() => selectSample?.Play(); - private partial class RulesetButton : ToolbarButton { protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); From 0d11b2b91c8e8a53ffcca580b30679f6614a2bb8 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 22 Jun 2024 01:57:14 +0900 Subject: [PATCH 012/670] Replace manual usages of `AudioFilter` with new ducking methods --- .../Collections/ManageCollectionsDialog.cs | 20 ++++++++----------- .../Dialog/PopupDialogDangerousButton.cs | 13 +++++------- osu.Game/Overlays/DialogOverlay.cs | 20 +++++-------------- 3 files changed, 18 insertions(+), 35 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index ea663f45fe..e777da05e5 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -7,11 +7,11 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Game.Audio.Effects; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Collections @@ -21,11 +21,12 @@ namespace osu.Game.Collections private const double enter_duration = 500; private const double exit_duration = 200; - private AudioFilter lowPassFilter = null!; - protected override string PopInSampleName => @"UI/overlay-big-pop-in"; protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + [Resolved] + private MusicController? musicController { get; set; } + public ManageCollectionsDialog() { Anchor = Anchor.Centre; @@ -110,19 +111,14 @@ namespace osu.Game.Collections }, } } - }, - lowPassFilter = new AudioFilter(audio.TrackMixer) + } }; } - public override bool IsPresent => base.IsPresent - // Safety for low pass filter potentially getting stuck in applied state due to - // transforms on `this` causing children to no longer be updated. - || lowPassFilter.IsAttached; - protected override void PopIn() { - lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); + musicController?.Duck(100, 1f); + this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); } @@ -131,7 +127,7 @@ namespace osu.Game.Collections { base.PopOut(); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); + musicController?.Unduck(100); this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index 19d7ea7a87..e01d4e2d66 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Audio.Effects; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -48,6 +47,9 @@ namespace osu.Game.Overlays.Dialog private partial class DangerousConfirmContainer : HoldToConfirmContainer { + [Resolved] + private MusicController musicController { get; set; } + public DangerousConfirmContainer() : base(isDangerousAction: true) { @@ -56,7 +58,6 @@ namespace osu.Game.Overlays.Dialog private Sample tickSample; private Sample confirmSample; private double lastTickPlaybackTime; - private AudioFilter lowPassFilter = null!; private bool mouseDown; [BackgroundDependencyLoader] @@ -64,8 +65,6 @@ namespace osu.Game.Overlays.Dialog { tickSample = audio.Samples.Get(@"UI/dialog-dangerous-tick"); confirmSample = audio.Samples.Get(@"UI/dialog-dangerous-select"); - - AddInternal(lowPassFilter = new AudioFilter(audio.SampleMixer)); } protected override void LoadComplete() @@ -76,13 +75,13 @@ namespace osu.Game.Overlays.Dialog protected override void AbortConfirm() { - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + musicController?.Unduck(); base.AbortConfirm(); } protected override void Confirm() { - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + musicController?.Duck(); confirmSample?.Play(); base.Confirm(); } @@ -126,8 +125,6 @@ namespace osu.Game.Overlays.Dialog if (Clock.CurrentTime - lastTickPlaybackTime < 30) return; - lowPassFilter.CutoffTo((int)(progress.NewValue * AudioFilter.MAX_LOWPASS_CUTOFF * 0.5)); - var channel = tickSample.GetChannel(); channel.Frequency.Value = 1 + progress.NewValue * 0.5f; diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 9ad532ae50..aa9bb99e01 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -10,9 +10,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Input.Events; -using osu.Game.Audio.Effects; namespace osu.Game.Overlays { @@ -23,15 +21,13 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/dialog-pop-in"; protected override string PopOutSampleName => "UI/dialog-pop-out"; - private AudioFilter lowPassFilter; + [Resolved] + private MusicController musicController { get; set; } public PopupDialog CurrentDialog { get; private set; } public override bool IsPresent => Scheduler.HasPendingTasks - || dialogContainer.Children.Count > 0 - // Safety for low pass filter potentially getting stuck in applied state due to - // transforms on `this` causing children to no longer be updated. - || lowPassFilter.IsAttached; + || dialogContainer.Children.Count > 0; public DialogOverlay() { @@ -49,12 +45,6 @@ namespace osu.Game.Overlays Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - AddInternal(lowPassFilter = new AudioFilter(audio.TrackMixer)); - } - public void Push(PopupDialog dialog) { if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return; @@ -105,13 +95,13 @@ namespace osu.Game.Overlays protected override void PopIn() { - lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); + musicController.Duck(100, 1f); } protected override void PopOut() { base.PopOut(); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); + musicController.Unduck(100); // PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present. if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible) From 2ffeb1b3618a7c3dffc08cef5862ea7cb575d20a Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sun, 23 Jun 2024 02:20:51 +0900 Subject: [PATCH 013/670] Add fallback behaviour for custom rulesets --- .../Toolbar/ToolbarRulesetSelector.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 93be108b71..7da6b76aaa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Toolbar private readonly Dictionary rulesetSelectionSample = new Dictionary(); private readonly Dictionary rulesetSelectionChannel = new Dictionary(); + private Sample defaultSelectSample; public ToolbarRulesetSelector() { @@ -68,6 +69,8 @@ namespace osu.Game.Overlays.Toolbar foreach (var r in Rulesets.AvailableRulesets) rulesetSelectionSample[r] = audio.Samples.Get($@"UI/ruleset-select-{r.ShortName}"); + defaultSelectSample = audio.Samples.Get(@"UI/default-select"); + Current.ValueChanged += playRulesetSelectionSample; } @@ -101,15 +104,24 @@ namespace osu.Game.Overlays.Toolbar private void playRulesetSelectionSample(ValueChangedEvent r) { + // Don't play sample on first setting of value if (r.OldValue == null) return; - if (rulesetSelectionChannel.TryGetValue(r.OldValue, out var sampleChannel)) - sampleChannel?.Stop(); + var channel = rulesetSelectionSample[r.NewValue]?.GetChannel(); - rulesetSelectionChannel[r.NewValue] = rulesetSelectionSample[r.NewValue].GetChannel(); - rulesetSelectionChannel[r.NewValue]?.Play(); + // Skip sample choking and ducking for the default/fallback sample + if (channel == null) + { + defaultSelectSample.Play(); + return; + } + foreach (var pair in rulesetSelectionChannel) + pair.Value?.Stop(); + + rulesetSelectionChannel[r.NewValue] = channel; + channel.Play(); musicController?.TimedDuck(600); } From 04efa61156517d2fe21ea4f4995a39670eca7ebb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jun 2024 08:22:13 +0300 Subject: [PATCH 014/670] Add different display for mod customisation --- .../TestSceneModCustomisationPanel.cs | 47 +++++ .../Localisation/ModSelectOverlayStrings.cs | 5 + .../Overlays/Mods/ModCustomisationHeader.cs | 86 ++++++++++ .../Overlays/Mods/ModCustomisationPanel.cs | 161 ++++++++++++++++++ .../Overlays/Mods/ModCustomisationSection.cs | 82 +++++++++ 5 files changed, 381 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModCustomisationHeader.cs create mode 100644 osu.Game/Overlays/Mods/ModCustomisationPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModCustomisationSection.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs new file mode 100644 index 0000000000..0066ecd556 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneModCustomisationPanel : OsuManualInputManagerTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20f), + Child = new ModCustomisationPanel + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 400f, + SelectedMods = { BindTarget = SelectedMods }, + } + }; + }); + + [Test] + public void TestDisplay() + { + AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); + AddStep("set DA", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); + AddStep("set FL+WU+DA+AD", () => SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }); + AddStep("set empty", () => SelectedMods.Value = Array.Empty()); + } + } +} diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index cf01081772..a2e1df42c6 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -75,6 +75,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"unranked_explanation"), @"Performance points will not be granted due to active mods."); + /// + /// "Customise" + /// + public static LocalisableString CustomisationPanelHeader => new TranslatableString(getKey(@"customisation_panel_header"), @"Customise"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs new file mode 100644 index 0000000000..e6534921f6 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.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 System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osu.Game.Localisation; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModCustomisationHeader : OsuHoverContainer + { + public override bool HandlePositionalInput => true; + + private Box background = null!; + private SpriteIcon icon = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + protected override IEnumerable EffectTargets => new[] { background }; + + public readonly BindableBool Expanded = new BindableBool(); + + [BackgroundDependencyLoader] + private void load() + { + CornerRadius = 10f; + Masking = true; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = ModSelectOverlayStrings.CustomisationPanelHeader, + UseFullGlyphHeight = false, + Font = OsuFont.Torus.With(size: 20f, weight: FontWeight.SemiBold), + Margin = new MarginPadding { Left = 20f }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(16f), + Margin = new MarginPadding { Right = 20f }, + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.ChevronDown, + RelativeSizeAxes = Axes.Both, + } + } + }; + + IdleColour = colourProvider.Dark3; + HoverColour = colourProvider.Light4; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(v => + { + icon.RotateTo(v.NewValue ? 180 : 0); + }, true); + + Action = Expanded.Toggle; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs new file mode 100644 index 0000000000..3694fe2bde --- /dev/null +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -0,0 +1,161 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModCustomisationPanel : VisibilityContainer + { + private const float header_height = 42f; + private const float content_vertical_padding = 20f; + + private Container content = null!; + private OsuScrollContainer scrollContainer = null!; + private FillFlowContainer sectionsFlow = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public readonly BindableBool Expanded = new BindableBool(); + + public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new ModCustomisationHeader + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.X, + Height = header_height, + Expanded = { BindTarget = Expanded }, + }, + content = new InputBlockingContainer + { + RelativeSizeAxes = Axes.X, + BorderColour = colourProvider.Dark3, + BorderThickness = 2f, + CornerRadius = 10f, + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0f, 5f), + Radius = 20f, + Roundness = 5f, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Dark4, + }, + scrollContainer = new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.X, + ScrollbarOverlapsContent = false, + Margin = new MarginPadding { Top = header_height }, + Child = sectionsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 40f), + Margin = new MarginPadding + { + Top = content_vertical_padding, + Bottom = 5f + content_vertical_padding + }, + } + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Expanded.BindValueChanged(_ => updateDisplay(), true); + SelectedMods.BindValueChanged(_ => updateMods(), true); + + FinishTransforms(true); + } + + protected override void PopIn() => this.FadeIn(300, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (Expanded.Value && !content.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + Expanded.Value = false; + + return base.OnMouseDown(e); + } + + private void updateDisplay() + { + content.ClearTransforms(); + + if (Expanded.Value) + { + content.AutoSizeDuration = 400; + content.AutoSizeEasing = Easing.OutQuint; + content.AutoSizeAxes = Axes.Y; + content.FadeEdgeEffectTo(0.25f, 120, Easing.OutQuint); + } + else + { + content.AutoSizeAxes = Axes.None; + content.ResizeHeightTo(header_height, 400, Easing.OutQuint); + content.FadeEdgeEffectTo(0f, 400, Easing.OutQuint); + } + } + + private void updateMods() + { + Expanded.Value = false; + sectionsFlow.Clear(); + + // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). + // Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent), + // which breaks user expectations when interacting with the overlay. + foreach (var mod in SelectedMods.Value) + { + var settings = mod.CreateSettingsControls().ToList(); + + if (settings.Count > 0) + sectionsFlow.Add(new ModCustomisationSection(mod, settings)); + } + } + + protected override void Update() + { + base.Update(); + scrollContainer.Height = Math.Min(scrollContainer.AvailableContent, DrawHeight - header_height); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModCustomisationSection.cs b/osu.Game/Overlays/Mods/ModCustomisationSection.cs new file mode 100644 index 0000000000..1dc97a8b0b --- /dev/null +++ b/osu.Game/Overlays/Mods/ModCustomisationSection.cs @@ -0,0 +1,82 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModCustomisationSection : CompositeDrawable + { + public readonly Mod Mod; + + private readonly IReadOnlyList settings; + + public ModCustomisationSection(Mod mod, IReadOnlyList settings) + { + Mod = mod; + + this.settings = settings; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + FillFlowContainer flow; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 8f), + Padding = new MarginPadding { Left = 7f }, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 20f, Right = 27f }, + Margin = new MarginPadding { Bottom = 4f }, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = Mod.Name, + Font = OsuFont.TorusAlternate.With(size: 20, weight: FontWeight.SemiBold), + }, + new ModSwitchTiny(Mod) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Active = { Value = true }, + Scale = new Vector2(0.5f), + } + } + }, + } + }; + + flow.AddRange(settings); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + FinishTransforms(true); + } + } +} From e23da93c0947b4692943bdf64ce897fbaac4db93 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jun 2024 08:22:50 +0300 Subject: [PATCH 015/670] Use new display on mod select overlay and remove old display --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 137 ++++----------- osu.Game/Overlays/Mods/ModSettingsArea.cs | 189 --------------------- 2 files changed, 35 insertions(+), 291 deletions(-) delete mode 100644 osu.Game/Overlays/Mods/ModSettingsArea.cs diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 145f58fb55..5ee52f32f5 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -109,15 +109,6 @@ namespace osu.Game.Overlays.Mods protected virtual IEnumerable CreateFooterButtons() { - if (AllowCustomisation) - { - yield return CustomisationButton = new ShearedToggleButton(BUTTON_WIDTH) - { - Text = ModSelectOverlayStrings.ModCustomisation, - Active = { BindTarget = customisationVisible } - }; - } - yield return deselectAllModsButton = new DeselectAllModsButton(this); } @@ -125,10 +116,8 @@ namespace osu.Game.Overlays.Mods public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); - private readonly BindableBool customisationVisible = new BindableBool(); private Bindable textSearchStartsActive = null!; - private ModSettingsArea modSettingsArea = null!; private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; private FillFlowContainer footerButtonFlow = null!; @@ -138,9 +127,9 @@ namespace osu.Game.Overlays.Mods private Container aboveColumnsContent = null!; private RankingInformationDisplay? rankingInformationDisplay; private BeatmapAttributesDisplay? beatmapAttributesDisplay; + private ModCustomisationPanel customisationPanel = null!; protected ShearedButton BackButton { get; private set; } = null!; - protected ShearedToggleButton? CustomisationButton { get; private set; } protected SelectAllModsButton? SelectAllModsButton { get; set; } private Sample? columnAppearSample; @@ -173,35 +162,8 @@ namespace osu.Game.Overlays.Mods columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in"); - AddRange(new Drawable[] - { - new ClickToReturnContainer - { - RelativeSizeAxes = Axes.Both, - HandleMouse = { BindTarget = customisationVisible }, - OnClicked = () => customisationVisible.Value = false - }, - modSettingsArea = new ModSettingsArea - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0 - }, - }); - MainAreaContent.AddRange(new Drawable[] { - aboveColumnsContent = new Container - { - RelativeSizeAxes = Axes.X, - Height = RankingInformationDisplay.HEIGHT, - Padding = new MarginPadding { Horizontal = 100 }, - Child = SearchTextBox = new ShearedSearchTextBox - { - HoldFocus = false, - Width = 300 - } - }, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -237,7 +199,27 @@ namespace osu.Game.Overlays.Mods } } } - } + }, + aboveColumnsContent = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 100, Bottom = 15f }, + Children = new Drawable[] + { + SearchTextBox = new ShearedSearchTextBox + { + HoldFocus = false, + Width = 300, + }, + customisationPanel = new ModCustomisationPanel + { + Alpha = 0f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 400, + } + } + }, }); FooterContent.Add(footerButtonFlow = new FillFlowContainer @@ -320,7 +302,7 @@ namespace osu.Game.Overlays.Mods // This is an optimisation to prevent refreshing the available settings controls when it can be // reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectOverlay). if (AllowCustomisation) - ((IBindable>)modSettingsArea.SelectedMods).BindTo(SelectedMods); + ((IBindable>)customisationPanel.SelectedMods).BindTo(SelectedMods); SelectedMods.BindValueChanged(_ => { @@ -347,7 +329,7 @@ namespace osu.Game.Overlays.Mods } }, true); - customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); + customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => { @@ -491,7 +473,7 @@ namespace osu.Game.Overlays.Mods private void updateCustomisation() { - if (CustomisationButton == null) + if (!AllowCustomisation) return; bool anyCustomisableModActive = false; @@ -506,38 +488,21 @@ namespace osu.Game.Overlays.Mods if (anyCustomisableModActive) { - customisationVisible.Disabled = false; + customisationPanel.Show(); - if (anyModPendingConfiguration && !customisationVisible.Value) - customisationVisible.Value = true; + if (anyModPendingConfiguration) + customisationPanel.Expanded.Value = true; } else { - if (customisationVisible.Value) - customisationVisible.Value = false; - - customisationVisible.Disabled = true; + customisationPanel.Expanded.Value = false; + customisationPanel.Hide(); } } private void updateCustomisationVisualState() { - const double transition_duration = 300; - - MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic); - - foreach (var button in footerButtonFlow) - { - if (button != CustomisationButton) - button.Enabled.Value = !customisationVisible.Value; - } - - float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0; - - modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic); - TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic); - - if (customisationVisible.Value) + if (customisationPanel.Expanded.Value) SearchTextBox.KillFocus(); else setTextBoxFocus(textSearchStartsActive.Value); @@ -693,6 +658,8 @@ namespace osu.Game.Overlays.Mods if (!allFiltered) nonFilteredColumnCount += 1; } + + customisationPanel.Expanded.Value = false; } #endregion @@ -758,10 +725,9 @@ namespace osu.Game.Overlays.Mods void hideOverlay(bool immediate) { - if (customisationVisible.Value) + if (customisationPanel.Expanded.Value) { - Debug.Assert(CustomisationButton != null); - CustomisationButton.TriggerClick(); + customisationPanel.Expanded.Value = false; if (!immediate) return; @@ -967,38 +933,5 @@ namespace osu.Game.Overlays.Mods updateState(); } } - - /// - /// A container which blocks and handles input, managing the "return from customisation" state change. - /// - private partial class ClickToReturnContainer : Container - { - public BindableBool HandleMouse { get; } = new BindableBool(); - - public Action? OnClicked { get; set; } - - public override bool HandlePositionalInput => base.HandlePositionalInput && HandleMouse.Value; - - protected override bool Handle(UIEvent e) - { - if (!HandleMouse.Value) - return base.Handle(e); - - switch (e) - { - case ClickEvent: - OnClicked?.Invoke(); - return true; - - case HoverEvent: - return false; - - case MouseEvent: - return true; - } - - return base.Handle(e); - } - } } } diff --git a/osu.Game/Overlays/Mods/ModSettingsArea.cs b/osu.Game/Overlays/Mods/ModSettingsArea.cs deleted file mode 100644 index d0e0f7e648..0000000000 --- a/osu.Game/Overlays/Mods/ModSettingsArea.cs +++ /dev/null @@ -1,189 +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 System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Overlays.Mods -{ - public partial class ModSettingsArea : CompositeDrawable - { - public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); - - public const float HEIGHT = 250; - - private readonly Box background; - private readonly FillFlowContainer modSettingsFlow; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - public override bool AcceptsFocus => true; - - public ModSettingsArea() - { - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - Anchor = Anchor.BottomRight; - Origin = Anchor.BottomRight; - - InternalChild = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new OsuScrollContainer(Direction.Horizontal) - { - RelativeSizeAxes = Axes.Both, - ScrollbarOverlapsContent = false, - ClampExtension = 100, - Child = modSettingsFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Padding = new MarginPadding { Vertical = 7, Horizontal = 70 }, - Spacing = new Vector2(7), - Direction = FillDirection.Horizontal - } - } - } - }; - } - - [BackgroundDependencyLoader] - private void load() - { - background.Colour = colourProvider.Dark3; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - SelectedMods.BindValueChanged(_ => updateMods(), true); - } - - private void updateMods() - { - modSettingsFlow.Clear(); - - // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). - // Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent), - // which breaks user expectations when interacting with the overlay. - foreach (var mod in SelectedMods.Value) - { - var settings = mod.CreateSettingsControls().ToList(); - - if (settings.Count > 0) - { - if (modSettingsFlow.Any()) - { - modSettingsFlow.Add(new Box - { - RelativeSizeAxes = Axes.Y, - Width = 2, - Colour = colourProvider.Dark4, - }); - } - - modSettingsFlow.Add(new ModSettingsColumn(mod, settings)); - } - } - } - - protected override bool OnMouseDown(MouseDownEvent e) => true; - protected override bool OnHover(HoverEvent e) => true; - - public partial class ModSettingsColumn : CompositeDrawable - { - public readonly Mod Mod; - - public ModSettingsColumn(Mod mod, IEnumerable settingsControls) - { - Mod = mod; - - Width = 250; - RelativeSizeAxes = Axes.Y; - Padding = new MarginPadding { Bottom = 7 }; - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension() - }, - Content = new[] - { - new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), - Children = new Drawable[] - { - new ModSwitchTiny(mod) - { - Active = { Value = true }, - Scale = new Vector2(0.6f), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft - }, - new OsuSpriteText - { - Text = mod.Name, - Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Bottom = 2 } - } - } - } - }, - new[] { Empty() }, - new Drawable[] - { - new OsuScrollContainer(Direction.Vertical) - { - RelativeSizeAxes = Axes.Both, - ClampExtension = 100, - Child = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 7 }, - ChildrenEnumerable = settingsControls, - Spacing = new Vector2(0, 7) - } - } - } - } - }; - } - } - } -} From 1298b98534246c5dc325a2cb9aae3333f1f38eeb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jun 2024 08:22:54 +0300 Subject: [PATCH 016/670] Update test coverage --- .../TestSceneFreeModSelectOverlay.cs | 2 +- .../TestSceneModSelectOverlay.cs | 63 +++++++++++-------- .../UserInterface/TestSceneModSettingsArea.cs | 42 ------------- 3 files changed, 38 insertions(+), 69 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index a4feffddfb..938ab1e9f4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); - AddAssert("customisation area not expanded", () => this.ChildrenOfType().Single().Height == 0); + AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded.Value); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index a1452ddb31..39017298ac 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -26,7 +25,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Tests.Mods; -using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -225,7 +223,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("dismiss mod customisation via toggle", () => { - InputManager.MoveMouseTo(modSelectOverlay.CustomisationButton.AsNonNull()); + InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); assertCustomisationToggleState(disabled: false, active: false); @@ -258,7 +256,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestDismissCustomisationViaDimmedArea() + public void TestDismissCustomisationViaClickingAway() { createScreen(); assertCustomisationToggleState(disabled: true, active: false); @@ -266,18 +264,23 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); assertCustomisationToggleState(disabled: false, active: true); - AddStep("move mouse to settings area", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); - AddStep("move mouse to dimmed area", () => - { - InputManager.MoveMouseTo(new Vector2( - modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.X, - (modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.Y + modSelectOverlay.ScreenSpaceDrawQuad.BottomLeft.Y) / 2)); - }); + AddStep("move mouse to search bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single())); AddStep("click", () => InputManager.Click(MouseButton.Left)); assertCustomisationToggleState(disabled: false, active: false); + } - AddStep("move mouse to first mod panel", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().First())); - AddAssert("first mod panel is hovered", () => modSelectOverlay.ChildrenOfType().First().IsHovered); + [Test] + public void TestDismissCustomisationWhenHidingOverlay() + { + createScreen(); + assertCustomisationToggleState(disabled: true, active: false); + + AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); + assertCustomisationToggleState(disabled: false, active: true); + + AddStep("hide overlay", () => modSelectOverlay.Hide()); + AddStep("show overlay again", () => modSelectOverlay.Show()); + assertCustomisationToggleState(disabled: false, active: false); } /// @@ -339,7 +342,7 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddStep("Select all fun mods", () => + AddStep("Select all difficulty-increase mods", () => { modSelectOverlay.ChildrenOfType() .Single(c => c.ModType == ModType.DifficultyIncrease) @@ -641,13 +644,16 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value), () => Is.EqualTo(1)); - AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); - assertCustomisationToggleState(false, true); + // todo: rewrite + AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType().Single().TriggerClick()); + assertCustomisationToggleState(disabled: false, active: true); + AddStep("hover over mod settings slider", () => { - var slider = modSelectOverlay.ChildrenOfType().Single().ChildrenOfType>().First(); + var slider = modSelectOverlay.ChildrenOfType().Single().ChildrenOfType>().First(); InputManager.MoveMouseTo(slider); }); + AddStep("press right arrow", () => InputManager.PressKey(Key.Right)); AddAssert("DT speed changed", () => !SelectedMods.Value.OfType().Single().SpeedChange.IsDefault); @@ -744,9 +750,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); assertCustomisationToggleState(disabled: false, active: true); - AddAssert("back button disabled", () => !modSelectOverlay.BackButton.Enabled.Value); AddStep("dismiss customisation area", () => InputManager.Key(Key.Escape)); + AddAssert("mod select still visible", () => modSelectOverlay.State.Value == Visibility.Visible); + AddStep("click back button", () => { InputManager.MoveMouseTo(modSelectOverlay.BackButton); @@ -870,8 +877,8 @@ namespace osu.Game.Tests.Visual.UserInterface // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. AddStep("force collection", GC.Collect); - AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick()); - AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() + AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType().Single().TriggerClick()); + AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); @@ -883,18 +890,23 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); AddStep("select DT + HD + DF", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModDeflate() }); + AddStep("open customisation panel", () => this.ChildrenOfType().Single().TriggerClick()); AddAssert("mod settings order: DT, HD, DF", () => { - var columns = this.ChildrenOfType().Single().ChildrenOfType(); + var columns = this.ChildrenOfType(); return columns.ElementAt(0).Mod is OsuModDoubleTime && columns.ElementAt(1).Mod is OsuModHidden && columns.ElementAt(2).Mod is OsuModDeflate; }); - AddStep("replace DT with NC", () => SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList()); + AddStep("replace DT with NC", () => + { + SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList(); + this.ChildrenOfType().Single().TriggerClick(); + }); AddAssert("mod settings order: NC, HD, DF", () => { - var columns = this.ChildrenOfType().Single().ChildrenOfType(); + var columns = this.ChildrenOfType(); return columns.ElementAt(0).Mod is OsuModNightcore && columns.ElementAt(1).Mod is OsuModHidden && columns.ElementAt(2).Mod is OsuModDeflate; @@ -915,8 +927,8 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { - AddAssert($"customisation toggle is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Disabled == disabled); - AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Value == active); + AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().State.Value == (disabled ? Visibility.Hidden : Visibility.Visible)); + AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded.Value == active); } private T getSelectedMod() where T : Mod => SelectedMods.Value.OfType().Single(); @@ -929,7 +941,6 @@ namespace osu.Game.Tests.Visual.UserInterface protected override bool ShowPresets => true; public new ShearedButton BackButton => base.BackButton; - public new ShearedToggleButton? CustomisationButton => base.CustomisationButton; } private class TestUnimplementedMod : Mod diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs deleted file mode 100644 index dac1f94c28..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettingsArea.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [TestFixture] - public partial class TestSceneModSettingsArea : OsuTestScene - { - [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); - - [Test] - public void TestModToggleArea() - { - ModSettingsArea modSettingsArea = null; - - AddStep("create content", () => Child = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = modSettingsArea = new ModSettingsArea() - }); - AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() }); - AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }); - AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty()); - } - } -} From 2efafcaf5b7ad4608d7c1305af691a693045444a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 23 Jun 2024 08:46:55 +0300 Subject: [PATCH 017/670] Remove stale comment --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 39017298ac..a9db957e12 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -644,7 +644,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value), () => Is.EqualTo(1)); - // todo: rewrite AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType().Single().TriggerClick()); assertCustomisationToggleState(disabled: false, active: true); From fbc99894279ab1da456cef2ceebc0615eefe24b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 01:01:26 +0300 Subject: [PATCH 018/670] Simplify default layout initialisation --- osu.Game/Skinning/ArgonSkinTransformer.cs | 41 ++++++++-------------- osu.Game/Skinning/LegacySkinTransformer.cs | 37 +++++-------------- 2 files changed, 23 insertions(+), 55 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs index 387a7a9c0b..8ca8f79b41 100644 --- a/osu.Game/Skinning/ArgonSkinTransformer.cs +++ b/osu.Game/Skinning/ArgonSkinTransformer.cs @@ -1,8 +1,8 @@ // 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.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK; @@ -17,34 +17,21 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (lookup) + if (lookup is SkinComponentsContainerLookup containerLookup + && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents + && containerLookup.Ruleset != null) { - case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) + return base.GetDrawableComponent(lookup) ?? new Container + { + RelativeSizeAxes = Axes.Both, + Child = new ArgonComboCounter { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.OfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Position = new Vector2(36, -66); - combo.Scale = new Vector2(1.3f); - } - }) - { - new ArgonComboCounter(), - }; - - return rulesetHUDComponents; - } - - break; + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(36, -66), + Scale = new Vector2(1.3f), + }, + }; } return base.GetDrawableComponent(lookup); diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 3ea316c0c7..dbfa52de84 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; -using osuTK; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning @@ -25,33 +24,15 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - switch (lookup) + if (lookup is SkinComponentsContainerLookup containerLookup + && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents + && containerLookup.Ruleset != null) { - case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - var rulesetHUDComponents = base.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.OfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Scale = new Vector2(1.28f); - } - }) - { - new LegacyComboCounter() - }; - - return rulesetHUDComponents; - } - - break; + return base.GetDrawableComponent(lookup) ?? new Container + { + RelativeSizeAxes = Axes.Both, + Child = new LegacyComboCounter(), + }; } return base.GetDrawableComponent(lookup); From e8de293be5f813731f97ab8132c569c914dca59a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 01:01:32 +0300 Subject: [PATCH 019/670] Remove pointless assert --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 462fd5ab64..17218b459a 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.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 System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Skinning; @@ -33,7 +32,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == CatchRuleset.SHORT_NAME); // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return Skin.GetDrawableComponent(lookup); } From 78e0126f16e90aca5459b288e42443bbf2b3387e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 04:24:58 +0300 Subject: [PATCH 020/670] Migrate combo counter layouts in custom skins to correct target-ruleset pairs --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Skinning/ArgonSkin.cs | 19 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 19 +++++++++++++++++++ osu.Game/Skinning/SkinImporter.cs | 2 ++ osu.Game/Skinning/SkinInfo.cs | 15 +++++++++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1ece81be50..606bc5e10c 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ namespace osu.Game.Database /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. + /// 42 2024-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes. /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 707281db31..743ce38810 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -12,6 +12,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -69,6 +70,24 @@ namespace osu.Game.Skinning // Purple new Color4(92, 0, 241, 255), }; + + if (skin.LayoutVersion < 20240625 + && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) + && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) + { + var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray(); + + if (comboCounters.Any()) + { + hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + hudLayout.Update(ruleset, comboCounters); + }); + } + } } public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b71b626b4e..c3e619431e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,6 +19,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -56,6 +57,24 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini") : base(skin, resources, fallbackStore, configurationFilename) { + if (resources != null + && skin.LayoutVersion < 20240625 + && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) + && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) + { + var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray(); + + if (comboCounters.Any()) + { + hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + hudLayout.Update(ruleset, comboCounters); + }); + } + } } protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 59c7f0ba26..714427f40d 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -223,6 +223,8 @@ namespace osu.Game.Skinning } } + s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION; + string newHash = ComputeHash(s); hadChanges = newHash != s.Hash; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 9763d3b57e..a3d5771b5e 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -39,6 +39,21 @@ namespace osu.Game.Skinning public bool Protected { get; set; } + /// + /// The latest version in YYYYMMDD format for skin layout migrations. + /// + /// + /// + /// 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets. + /// + /// + public const int LATEST_LAYOUT_VERSION = 20240625; + + /// + /// A version in YYYYMMDD format for applying skin layout migrations. + /// + public int LayoutVersion { get; set; } + public virtual Skin CreateInstance(IStorageResourceProvider resources) { var type = string.IsNullOrEmpty(InstantiationInfo) From fc2202e0cc9cdf1191675272607aa7e51f2037e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 05:54:56 +0300 Subject: [PATCH 021/670] Fix migration logic overwriting existing components in ruleset targets --- osu.Game/Skinning/ArgonSkin.cs | 6 +++++- osu.Game/Skinning/LegacySkin.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 743ce38810..4cd54c06f0 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -84,7 +84,11 @@ namespace osu.Game.Skinning resources.RealmAccess.Run(r => { foreach (var ruleset in r.All()) - hudLayout.Update(ruleset, comboCounters); + { + hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) + ? rulesetComponents.Concat(comboCounters).ToArray() + : comboCounters); + } }); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c3e619431e..f148bad96e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -71,7 +71,11 @@ namespace osu.Game.Skinning resources.RealmAccess.Run(r => { foreach (var ruleset in r.All()) - hudLayout.Update(ruleset, comboCounters); + { + hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) + ? rulesetComponents.Concat(comboCounters).ToArray() + : comboCounters); + } }); } } From dc1fb4fdca462a8d8123e695177aa61f951a78da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 05:54:59 +0300 Subject: [PATCH 022/670] Add test coverage --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 3c97700fb0..2470c320cc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -13,12 +13,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Skinning.Components; @@ -39,6 +41,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Resolved] + private SkinManager skins { get; set; } = null!; + private SkinComponentsContainer targetContainer => Player.ChildrenOfType().First(); [SetUpSteps] @@ -46,6 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); + AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault()); AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded); AddStep("reload skin editor", () => @@ -369,6 +375,84 @@ namespace osu.Game.Tests.Visual.Gameplay () => Is.EqualTo(3)); } + private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null); + + private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType() + .Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null); + + [Test] + public void TestMigrationArgon() + { + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); + AddStep("add combo to global hud target", () => + { + globalHUDTarget.Add(new ArgonComboCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + }); + + Live modifiedSkin = null!; + + AddStep("select another skin", () => + { + modifiedSkin = skins.CurrentSkinInfo.Value; + skins.CurrentSkinInfo.SetDefault(); + }); + AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); + AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter)); + AddAssert("ruleset hud target contains both combos", () => + { + var target = rulesetHUDTarget; + + return target.Components.Count == 2 && + target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && + target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; + }); + AddStep("save skin", () => skinEditor.Save()); + AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + } + + [Test] + public void TestMigrationLegacy() + { + AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); + AddStep("add combo to global hud target", () => + { + globalHUDTarget.Add(new LegacyComboCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + }); + + Live modifiedSkin = null!; + + AddStep("select another skin", () => + { + modifiedSkin = skins.CurrentSkinInfo.Value; + skins.CurrentSkinInfo.SetDefault(); + }); + AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); + AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter)); + AddAssert("ruleset hud target contains both combos", () => + { + var target = rulesetHUDTarget; + + return target.Components.Count == 2 && + target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && + target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; + }); + AddStep("save skin", () => skinEditor.Save()); + AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler From 0a72394c8a037950365c0108a1180ee20e197412 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 06:04:39 +0300 Subject: [PATCH 023/670] Fix customisation panel conflicting with beatmap attributes when collapsed --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 5ee52f32f5..8c3c81f2e1 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -372,6 +372,7 @@ namespace osu.Game.Overlays.Mods footerContentFlow.LayoutDuration = 200; footerContentFlow.LayoutEasing = Easing.OutQuint; footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal; + aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f }; } } From 57ee794398d70455280d66628497af762077789b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 06:08:13 +0300 Subject: [PATCH 024/670] Add extra margin to avoid 1px artifacts --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 3694fe2bde..7f3a176356 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -74,7 +74,9 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.X, ScrollbarOverlapsContent = false, - Margin = new MarginPadding { Top = header_height }, + // The +2f is a workaround for masking issues (see https://github.com/ppy/osu-framework/issues/1675#issuecomment-910023157) + // Note that this actually causes the full scroll range to be reduced by 2px at the bottom, but it's not really noticeable. + Margin = new MarginPadding { Top = header_height + 2f }, Child = sectionsFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, From 3f06a0ef9e89221bd71b3895463c90d9acd5297b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 06:15:02 +0300 Subject: [PATCH 025/670] Block certain operations when customisation panel is open Normally I would just block keyboard input from going past `ModCustomisationPanel`, but it's a little complicated here since I'm dealing with global action key binding presses, and I also still want actions like `GlobalAction.Back` to get past the customisation panel even if it's expanded. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 8c3c81f2e1..56e0a88b5a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -691,7 +691,7 @@ namespace osu.Game.Overlays.Mods // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { - if (!SearchTextBox.HasFocus) + if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) { deselectAllModsButton.TriggerClick(); return true; @@ -762,6 +762,9 @@ namespace osu.Game.Overlays.Mods if (e.Repeat || e.Key != Key.Tab) return false; + if (customisationPanel.Expanded.Value) + return true; + // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) setTextBoxFocus(!SearchTextBox.HasFocus); return true; From 7ddb6cb7e7c342b45e1550d8669e05343e42b581 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 06:56:39 +0300 Subject: [PATCH 026/670] Scroll mod setting dropdown into view when open --- .../Configuration/SettingSourceAttribute.cs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 1e425c88a6..f87c8de0cb 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -13,8 +13,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osuTK; namespace osu.Game.Configuration { @@ -274,8 +276,37 @@ namespace osu.Game.Configuration private partial class ModDropdownControl : DropdownControl { - // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). - protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); + protected override DropdownMenu CreateMenu() => new ModDropdownMenu(); + + private partial class ModDropdownMenu : OsuDropdownMenu + { + public ModDropdownMenu() + { + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + MaxHeight = 100; + } + + protected override void UpdateSize(Vector2 newSize) + { + base.UpdateSize(newSize); + + // todo: probably move this to OsuDropdown so that settings overlay can benefit from this as well. + if (newSize.Y > 0) + { + var scroll = this.FindClosestParent(); + + if (scroll != null) + { + const float padding = 15; + + float target = scroll.GetChildPosInContent(this, new Vector2(0, newSize.Y + padding)); + + if (target > scroll.Current + scroll.DisplayableContent) + scroll.ScrollTo(target - scroll.DisplayableContent); + } + } + } + } } } } From a277d9df13ba0257dee47c3b40adaa72ceb2f931 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 25 Jun 2024 07:13:34 +0300 Subject: [PATCH 027/670] Kill focus from components when clicking on an empty space in the panel --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 7f3a176356..ec3499bc57 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Mods Height = header_height, Expanded = { BindTarget = Expanded }, }, - content = new InputBlockingContainer + content = new FocusGrabbingContainer { RelativeSizeAxes = Axes.X, BorderColour = colourProvider.Dark3, @@ -159,5 +159,11 @@ namespace osu.Game.Overlays.Mods base.Update(); scrollContainer.Height = Math.Min(scrollContainer.AvailableContent, DrawHeight - header_height); } + + private partial class FocusGrabbingContainer : InputBlockingContainer + { + public override bool RequestsFocus => true; + public override bool AcceptsFocus => true; + } } } From 167ffac21890cf959f159ccd2a63c72d37453346 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 28 Jun 2024 06:35:12 +0300 Subject: [PATCH 028/670] Reorder container layout for popovers to recognize clicks on customisation panel Basically moves `PopoverContainer` to cover both the columns and the customisation panel, so that if the customisation panel is clicked on, the popover container will notice that and hide popovers like the mod preset popover. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 100 +++++++++++---------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 56e0a88b5a..f447f6f722 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -162,64 +162,68 @@ namespace osu.Game.Overlays.Mods columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in"); - MainAreaContent.AddRange(new Drawable[] + MainAreaContent.Add(new OsuContextMenuContainer { - new OsuContextMenuContainer + RelativeSizeAxes = Axes.Both, + Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - Child = new PopoverContainer + Children = new Drawable[] { - Padding = new MarginPadding + new Container { - Top = RankingInformationDisplay.HEIGHT + PADDING, - Bottom = PADDING - }, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Children = new Drawable[] - { - columnScroll = new ColumnScrollContainer + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Masking = false, - ClampExtension = 100, - ScrollbarOverlapsContent = false, - Child = columnFlow = new ColumnFlowContainer + Top = RankingInformationDisplay.HEIGHT + PADDING, + Bottom = PADDING + }, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + columnScroll = new ColumnScrollContainer { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - Shear = new Vector2(OsuGame.SHEAR, 0), - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Margin = new MarginPadding { Horizontal = 70 }, - Padding = new MarginPadding { Bottom = 10 }, - ChildrenEnumerable = createColumns() + RelativeSizeAxes = Axes.Both, + Masking = false, + ClampExtension = 100, + ScrollbarOverlapsContent = false, + Child = columnFlow = new ColumnFlowContainer + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Shear = new Vector2(OsuGame.SHEAR, 0), + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Margin = new MarginPadding { Horizontal = 70 }, + Padding = new MarginPadding { Bottom = 10 }, + ChildrenEnumerable = createColumns() + } + } + } + }, + aboveColumnsContent = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 100, Bottom = 15f }, + Children = new Drawable[] + { + SearchTextBox = new ShearedSearchTextBox + { + HoldFocus = false, + Width = 300, + }, + customisationPanel = new ModCustomisationPanel + { + Alpha = 0f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 400, } } } - } - }, - aboveColumnsContent = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 100, Bottom = 15f }, - Children = new Drawable[] - { - SearchTextBox = new ShearedSearchTextBox - { - HoldFocus = false, - Width = 300, - }, - customisationPanel = new ModCustomisationPanel - { - Alpha = 0f, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 400, - } - } - }, + }, + } }); FooterContent.Add(footerButtonFlow = new FillFlowContainer From 86b8357b8b7fd39be80a7881103d289ba4342a12 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 28 Jun 2024 08:52:52 +0300 Subject: [PATCH 029/670] Improve UX & input handling when customisation panel is open --- .../TestSceneModCustomisationPanel.cs | 1 + .../Overlays/Mods/ModCustomisationPanel.cs | 43 ++++++++++++++++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 33 ++++++-------- 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 0066ecd556..360c28acfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -30,6 +30,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Width = 400f, + State = { Value = Visibility.Visible }, SelectedMods = { BindTarget = SelectedMods }, } }; diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index ec3499bc57..a5a524e109 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -10,16 +10,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osuTK; namespace osu.Game.Overlays.Mods { - public partial class ModCustomisationPanel : VisibilityContainer + public partial class ModCustomisationPanel : OverlayContainer, IKeyBindingHandler { private const float header_height = 42f; private const float content_vertical_padding = 20f; @@ -35,6 +37,12 @@ namespace osu.Game.Overlays.Mods public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); + public override bool HandlePositionalInput => Expanded.Value; + + public override bool HandleNonPositionalInput => Expanded.Value; + + protected override bool BlockPositionalInput => true; + [BackgroundDependencyLoader] private void load() { @@ -63,6 +71,7 @@ namespace osu.Game.Overlays.Mods Radius = 20f, Roundness = 5f, }, + Expanded = { BindTarget = Expanded }, Children = new Drawable[] { new Box @@ -110,12 +119,30 @@ namespace osu.Game.Overlays.Mods public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnClick(ClickEvent e) { - if (Expanded.Value && !content.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - Expanded.Value = false; + Expanded.Value = false; + return base.OnClick(e); + } - return base.OnMouseDown(e); + protected override bool OnKeyDown(KeyDownEvent e) => true; + + protected override bool OnScroll(ScrollEvent e) => true; + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.Back: + Expanded.Value = false; + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { } private void updateDisplay() @@ -162,8 +189,10 @@ namespace osu.Game.Overlays.Mods private partial class FocusGrabbingContainer : InputBlockingContainer { - public override bool RequestsFocus => true; - public override bool AcceptsFocus => true; + public IBindable Expanded { get; } = new BindableBool(); + + public override bool RequestsFocus => Expanded.Value; + public override bool AcceptsFocus => Expanded.Value; } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f447f6f722..b0d58480db 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -29,6 +29,7 @@ using osu.Game.Localisation; using osu.Game.Rulesets.Mods; using osu.Game.Utils; using osuTK; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Overlays.Mods @@ -215,7 +216,6 @@ namespace osu.Game.Overlays.Mods }, customisationPanel = new ModCustomisationPanel { - Alpha = 0f, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 400, @@ -508,9 +508,17 @@ namespace osu.Game.Overlays.Mods private void updateCustomisationVisualState() { if (customisationPanel.Expanded.Value) + { + columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); + SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.KillFocus(); + } else + { + columnScroll.FadeColour(Color4.White, 400, Easing.OutQuint); + SearchTextBox.FadeColour(Color4.White, 400, Easing.OutQuint); setTextBoxFocus(textSearchStartsActive.Value); + } } /// @@ -678,16 +686,12 @@ namespace osu.Game.Overlays.Mods switch (e.Action) { + // If the customisation panel is expanded, the back action will be handled by it first. case GlobalAction.Back: - // Pressing the back binding should only go back one step at a time. - hideOverlay(false); - return true; - // This is handled locally here because this overlay is being registered at the game level // and therefore takes away keyboard focus from the screen stack. case GlobalAction.ToggleModSelection: - // Pressing toggle should completely hide the overlay in one shot. - hideOverlay(true); + hideOverlay(); return true; // This is handled locally here due to conflicts in input handling between the search text box and the deselect all mods button. @@ -710,7 +714,7 @@ namespace osu.Game.Overlays.Mods // If there is no search in progress, it should exit the dialog (a bit weird, but this is the expectation from stable). if (string.IsNullOrEmpty(SearchTerm)) { - hideOverlay(true); + hideOverlay(); return true; } @@ -728,18 +732,7 @@ namespace osu.Game.Overlays.Mods return base.OnPressed(e); - void hideOverlay(bool immediate) - { - if (customisationPanel.Expanded.Value) - { - customisationPanel.Expanded.Value = false; - - if (!immediate) - return; - } - - BackButton.TriggerClick(); - } + void hideOverlay() => BackButton.TriggerClick(); } /// From f462aa59df34508bd4d792dcc22d1f645ac2637c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 28 Jun 2024 11:23:49 +0300 Subject: [PATCH 030/670] Add test coverage for the issues that were pointed out recently --- .../TestSceneModSelectOverlay.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index a9db957e12..dedad3a40a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -25,6 +25,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Tests.Mods; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -761,6 +762,19 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); } + [Test] + public void TestCloseViaToggleModSelectionBinding() + { + createScreen(); + changeRuleset(0); + + AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick()); + assertCustomisationToggleState(disabled: false, active: true); + + AddStep("press F1", () => InputManager.Key(Key.F1)); + AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); + } + /// /// Covers columns hiding/unhiding on changes of . /// @@ -912,6 +926,68 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestOpeningCustomisationHidesPresetPopover() + { + createScreen(); + + AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); + AddStep("click new preset", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("preset popover shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.True); + + AddStep("click customisation header", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("preset popover hidden", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + AddAssert("customisation panel shown", () => this.ChildrenOfType().Single().State.Value, () => Is.EqualTo(Visibility.Visible)); + } + + [Test] + public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsActive) + { + AddStep($"text search starts active = {textSearchStartsActive}", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, textSearchStartsActive)); + createScreen(); + + AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() }); + AddStep("open customisation panel", () => this.ChildrenOfType().Single().TriggerClick()); + AddAssert("search lost focus", () => !this.ChildrenOfType().Single().HasFocus); + + AddStep("press tab", () => InputManager.Key(Key.Tab)); + AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); + + AddStep("press q", () => InputManager.Key(Key.Q)); + AddAssert("easy not selected", () => SelectedMods.Value.Single() is OsuModDoubleTime); + + // the "deselect all mods" action is intentionally disabled when customisation panel is open to not conflict with pressing backspace to delete characters in a textbox. + // this is supposed to be handled by the textbox itself especially since it's focused and thus prioritised in input queue, + // but it's not for some reason, and figuring out why is probably not going to be a pleasant experience (read TextBox.OnKeyDown for a head start). + AddStep("press backspace", () => InputManager.Key(Key.BackSpace)); + AddAssert("mods not deselected", () => SelectedMods.Value.Single() is OsuModDoubleTime); + + AddStep("move mouse to scroll bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType().Single().ScreenSpaceDrawQuad.BottomLeft + new Vector2(10f, -5f))); + + AddStep("scroll down", () => InputManager.ScrollVerticalBy(-10f)); + AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType().Single().IsScrolledToStart()); + + AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); + AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); + AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded.Value); + + if (textSearchStartsActive) + AddAssert("search focused", () => this.ChildrenOfType().Single().HasFocus); + else + AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); + } + private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => modSelectOverlay.ChildrenOfType().Any() && modSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded) From df972152984a79019e30a1edc25ebab72479bf14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 15:36:30 +0200 Subject: [PATCH 031/670] Use room watching functionality to receive realtime daily challenge updates --- .../TestSceneDailyChallengeCarousel.cs | 27 +++-- .../TestSceneDailyChallengeEventFeed.cs | 31 ++++-- .../TestSceneDailyChallengeScoreBreakdown.cs | 13 ++- .../Online/Metadata/OnlineMetadataClient.cs | 2 +- .../DailyChallenge/DailyChallenge.cs | 100 +++++++++++++++++- .../DailyChallenge/DailyChallengeEventFeed.cs | 12 +-- .../DailyChallengeScoreBreakdown.cs | 10 +- .../DailyChallenge/Events/NewScoreEvent.cs | 23 ++++ 8 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/Events/NewScoreEvent.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs index b9143945c4..d53e386ad4 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeCarousel.cs @@ -11,10 +11,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; -using osu.Game.Tests.Resources; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; namespace osu.Game.Tests.Visual.DailyChallenge { @@ -129,19 +130,27 @@ namespace osu.Game.Tests.Visual.DailyChallenge }); AddStep("add normal score", () => { - var testScore = TestResources.CreateTestScoreInfo(); - testScore.TotalScore = RNG.Next(1_000_000); + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); - feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null)); - breakdown.AddNewScore(testScore); + feed.AddNewScore(ev); + breakdown.AddNewScore(ev); }); AddStep("add new user best", () => { - var testScore = TestResources.CreateTestScoreInfo(); - testScore.TotalScore = RNG.Next(1_000_000); + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), RNG.Next(1, 1000)); - feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000))); - breakdown.AddNewScore(testScore); + feed.AddNewScore(ev); + breakdown.AddNewScore(ev); }); } diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index 85499f0588..d9a8ccd510 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -7,8 +7,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.DailyChallenge @@ -50,26 +52,41 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("add normal score", () => { - var testScore = TestResources.CreateTestScoreInfo(); - testScore.TotalScore = RNG.Next(1_000_000); + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); - feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, null)); + feed.AddNewScore(ev); }); AddStep("add new user best", () => { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), RNG.Next(11, 1000)); + var testScore = TestResources.CreateTestScoreInfo(); testScore.TotalScore = RNG.Next(1_000_000); - feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 1000))); + feed.AddNewScore(ev); }); AddStep("add top 10 score", () => { - var testScore = TestResources.CreateTestScoreInfo(); - testScore.TotalScore = RNG.Next(1_000_000); + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), RNG.Next(1, 10)); - feed.AddNewScore(new DailyChallengeEventFeed.NewScoreEvent(testScore, RNG.Next(1, 10))); + feed.AddNewScore(ev); }); } } diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 5523d02694..81ec95d8d2 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -7,9 +7,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; -using osu.Game.Tests.Resources; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; namespace osu.Game.Tests.Visual.DailyChallenge { @@ -51,10 +52,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); AddStep("add new score", () => { - var testScore = TestResources.CreateTestScoreInfo(); - testScore.TotalScore = RNG.Next(1_000_000); + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); - breakdown.AddNewScore(testScore); + breakdown.AddNewScore(ev); }); } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 80fcf7571d..911b13ecd8 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -256,7 +256,7 @@ namespace osu.Game.Online.Metadata throw new OperationCanceledException(); Debug.Assert(connection != null); - await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom)).ConfigureAwait(false); + await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom), id).ConfigureAwait(false); } public override async Task DisconnectRequested() diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index e2927617f8..b8d0dbbe7d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -8,21 +8,29 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; 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.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -47,6 +55,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private Sample? sampleStart; private IDisposable? userModsSelectOverlayRegistration; + private DailyChallengeScoreBreakdown breakdown = null!; + private DailyChallengeEventFeed feed = null!; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -68,6 +79,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private IOverlayManager? overlayManager { get; set; } + [Resolved] + private MetadataClient metadataClient { get; set; } = null!; + + [Resolved] + private UserLookupCache userLookupCache { get; set; } = null!; + public override bool DisallowExternalBeatmapRulesetChanges => true; public DailyChallenge(Room room) @@ -162,9 +179,39 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { new Drawable?[] { - new DailyChallengeTimeRemainingRing + new GridContainer { RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RowDimensions = + [ + new Dimension(), + new Dimension() + ], + Content = new[] + { + new Drawable[] + { + new DailyChallengeCarousel + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new DailyChallengeTimeRemainingRing(), + breakdown = new DailyChallengeScoreBreakdown(), + } + } + }, + [ + feed = new DailyChallengeEventFeed + { + RelativeSizeAxes = Axes.Both, + } + ], + }, }, null, // Middle column (leaderboard) @@ -275,6 +322,33 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)); userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } + + metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; + } + + private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) + { + if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID) + return; + + userLookupCache.GetUserAsync(e.UserID).ContinueWith(t => + { + if (t.Exception != null) + { + Logger.Log($@"Could not display room score set event: {t.Exception}", LoggingTarget.Network); + return; + } + + APIUser? user = t.GetResultSafely(); + if (user == null) return; + + var ev = new NewScoreEvent(e.ScoreID, user, e.TotalScore, e.NewRank); + Schedule(() => + { + breakdown.AddNewScore(ev); + feed.AddNewScore(ev); + }); + }); } protected override void LoadComplete() @@ -294,6 +368,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); + applyLoopingToTrack(); } public override void OnEntering(ScreenTransitionEvent e) @@ -303,6 +378,25 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge waves.Show(); roomManager.JoinRoom(room); applyLoopingToTrack(); + + metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t => + { + if (t.Exception != null) + { + Logger.Error(t.Exception, @"Failed to subscribe to room updates", LoggingTarget.Network); + return; + } + + MultiplayerPlaylistItemStats[] stats = t.GetResultSafely(); + var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID); + if (itemStats == null) return; + + Schedule(() => breakdown.SetInitialCounts(itemStats.TotalScoreDistribution)); + }); + + beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + userModsSelectOverlay.SelectedItem.Value = playlistItem; } public override void OnResuming(ScreenTransitionEvent e) @@ -327,6 +421,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); roomManager.PartRoom(); + metadataClient.EndWatchingMultiplayerRoom(room.RoomID.Value!.Value).FireAndForget(); return base.OnExiting(e); } @@ -375,6 +470,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge base.Dispose(isDisposing); userModsSelectOverlayRegistration?.Dispose(); + + if (metadataClient.IsNotNull()) + metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet; } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index b415b15b65..c38a921e43 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -9,8 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osu.Game.Users.Drawables; using osuTK; @@ -70,8 +69,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } - public record NewScoreEvent(IScoreInfo Score, int? NewRank); - private partial class DailyChallengeEventFeedFlow : FillFlowContainer { public override IEnumerable FlowingChildren => base.FlowingChildren.Reverse(); @@ -98,8 +95,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge InternalChildren = new Drawable[] { - // TODO: cast is temporary, will be removed later - new ClickableAvatar((APIUser)newScore.Score.User) + new ClickableAvatar(newScore.User) { Size = new Vector2(16), Masking = true, @@ -117,9 +113,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } }; - text.AddUserLink(newScore.Score.User); + text.AddUserLink(newScore.User); text.AddText(" got "); - text.AddLink($"{newScore.Score.TotalScore:N0} points", () => { }); // TODO: present the score here + text.AddLink($"{newScore.TotalScore:N0} points", () => { }); // TODO: present the score here if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index d251a10f9a..0c7202f7cf 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Metadata; using osu.Game.Overlays; -using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge @@ -67,15 +67,15 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } - public void AddNewScore(IScoreInfo scoreInfo) + public void AddNewScore(NewScoreEvent newScoreEvent) { - int targetBin = (int)Math.Clamp(Math.Floor((float)scoreInfo.TotalScore / 100000), 0, bin_count - 1); + int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); bins[targetBin] += 1; updateCounts(); var text = new OsuSpriteText { - Text = scoreInfo.TotalScore.ToString(@"N0"), + Text = newScoreEvent.TotalScore.ToString(@"N0"), Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Font = OsuFont.Default.With(size: 30), @@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void updateCounts() { - long max = bins.Max(); + long max = Math.Max(bins.Max(), 1); for (int i = 0; i < bin_count; ++i) barsContainer[i].UpdateCounts(bins[i], max); } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/Events/NewScoreEvent.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/Events/NewScoreEvent.cs new file mode 100644 index 0000000000..bc4c4e1a1e --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/Events/NewScoreEvent.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge.Events +{ + public class NewScoreEvent + { + public NewScoreEvent(long scoreID, APIUser user, long totalScore, int? newRank) + { + ScoreID = scoreID; + User = user; + TotalScore = totalScore; + NewRank = newRank; + } + + public long ScoreID { get; } + public APIUser User { get; } + public long TotalScore { get; } + public int? NewRank { get; } + } +} From 892659de0f22b4bc584dc98a096c32a696d7c893 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 27 Jun 2024 08:15:12 +0300 Subject: [PATCH 032/670] Adjust footer design to display well with the rest of the game --- osu.Game/Screens/Footer/ScreenBackButton.cs | 9 +++------ osu.Game/Screens/Footer/ScreenFooter.cs | 4 ++-- osu.Game/Screens/Footer/ScreenFooterButton.cs | 15 +++++++-------- .../SelectV2/Footer/ScreenFooterButtonMods.cs | 12 +++++------- .../SelectV2/Footer/ScreenFooterButtonRandom.cs | 6 +++--- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenBackButton.cs b/osu.Game/Screens/Footer/ScreenBackButton.cs index c5e613ea51..bf29186bb1 100644 --- a/osu.Game/Screens/Footer/ScreenBackButton.cs +++ b/osu.Game/Screens/Footer/ScreenBackButton.cs @@ -17,13 +17,10 @@ namespace osu.Game.Screens.Footer { public partial class ScreenBackButton : ShearedButton { - // todo: see https://github.com/ppy/osu-framework/issues/3271 - private const float torus_scale_factor = 1.2f; - public const float BUTTON_WIDTH = 240; public ScreenBackButton() - : base(BUTTON_WIDTH, 70) + : base(BUTTON_WIDTH) { } @@ -42,14 +39,14 @@ namespace osu.Game.Screens.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(20f), + Size = new Vector2(17f), Icon = FontAwesome.Solid.ChevronLeft, }, new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.TorusAlternate.With(size: 20 * torus_scale_factor), + Font = OsuFont.TorusAlternate.With(size: 17), Text = CommonStrings.Back, UseFullGlyphHeight = false, } diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 594cb3b9c9..c7090ba344 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Footer private const int padding = 60; private const float delay_per_button = 30; - public const int HEIGHT = 60; + public const int HEIGHT = 50; private readonly List overlays = new List(); @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Footer }, backButton = new ScreenBackButton { - Margin = new MarginPadding { Bottom = 10f, Left = 12f }, + Margin = new MarginPadding { Bottom = 15f, Left = 12f }, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Action = () => OnBack?.Invoke(), diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 1e5576e47a..cd1de2454b 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -28,8 +28,8 @@ namespace osu.Game.Screens.Footer private const float shear = OsuGame.SHEAR; protected const int CORNER_RADIUS = 10; - protected const int BUTTON_HEIGHT = 90; - protected const int BUTTON_WIDTH = 140; + protected const int BUTTON_HEIGHT = 75; + protected const int BUTTON_WIDTH = 116; public Bindable OverlayState = new Bindable(); @@ -116,19 +116,18 @@ namespace osu.Game.Screens.Footer { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Y = 42, + Y = 35, AutoSizeAxes = Axes.Both, Child = text = new OsuSpriteText { - // figma design says the size is 16, but due to the issues with font sizes 19 matches better - Font = OsuFont.TorusAlternate.With(size: 19), + Font = OsuFont.TorusAlternate.With(size: 16), AlwaysPresent = true } }, icon = new SpriteIcon { - Y = 12, - Size = new Vector2(20), + Y = 10, + Size = new Vector2(16), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre }, @@ -140,7 +139,7 @@ namespace osu.Game.Screens.Footer Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Y = -CORNER_RADIUS, - Size = new Vector2(120, 6), + Size = new Vector2(100, 5), Masking = true, CornerRadius = 3, Child = bar = new Box diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index 841f0297e8..0992203dbc 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -32,9 +32,7 @@ namespace osu.Game.Screens.SelectV2.Footer { public partial class ScreenFooterButtonMods : ScreenFooterButton, IHasCurrentValue> { - // todo: see https://github.com/ppy/osu-framework/issues/3271 - private const float torus_scale_factor = 1.2f; - private const float bar_height = 37f; + private const float bar_height = 30f; private const float mod_display_portion = 0.65f; private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); @@ -112,7 +110,7 @@ namespace osu.Game.Screens.SelectV2.Footer Origin = Anchor.Centre, Shear = -BUTTON_SHEAR, UseFullGlyphHeight = false, - Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) + Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold) } }, new Container @@ -133,7 +131,7 @@ namespace osu.Game.Screens.SelectV2.Footer Anchor = Anchor.Centre, Origin = Anchor.Centre, Shear = -BUTTON_SHEAR, - Scale = new Vector2(0.6f), + Scale = new Vector2(0.5f), Current = { BindTarget = Current }, ExpansionMode = ExpansionMode.AlwaysContracted, }, @@ -142,7 +140,7 @@ namespace osu.Game.Screens.SelectV2.Footer Anchor = Anchor.Centre, Origin = Anchor.Centre, Shear = -BUTTON_SHEAR, - Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold), Mods = { BindTarget = Current }, } } @@ -335,7 +333,7 @@ namespace osu.Game.Screens.SelectV2.Footer Text = ModSelectOverlayStrings.Unranked.ToUpper(), Margin = new MarginPadding { Horizontal = 15 }, UseFullGlyphHeight = false, - Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), + Font = OsuFont.Torus.With(size: 14f, weight: FontWeight.Bold), Colour = Color4.Black, } }; diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs index e8e850a9ce..dbdb6fe79b 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonRandom.cs @@ -42,7 +42,7 @@ namespace osu.Game.Screens.SelectV2.Footer { randomSpriteText = new OsuSpriteText { - Font = OsuFont.TorusAlternate.With(size: 19), + Font = OsuFont.TorusAlternate.With(size: 16), AlwaysPresent = true, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -50,7 +50,7 @@ namespace osu.Game.Screens.SelectV2.Footer }, rewindSpriteText = new OsuSpriteText { - Font = OsuFont.TorusAlternate.With(size: 19), + Font = OsuFont.TorusAlternate.With(size: 16), AlwaysPresent = true, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -75,7 +75,7 @@ namespace osu.Game.Screens.SelectV2.Footer AlwaysPresent = true, // make sure the button is sized large enough to always show this Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Font = OsuFont.TorusAlternate.With(size: 19), + Font = OsuFont.TorusAlternate.With(size: 16), }); fallingRewind.FadeOutFromOne(fade_time, Easing.In); From 68b8a4fb2ac3b06904695d0884717669a968e265 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 08:35:48 +0300 Subject: [PATCH 033/670] Use `ScreenFooter` for displaying footer buttons from overlays --- .../Overlays/Mods/ShearedOverlayContainer.cs | 40 +++++++-- osu.Game/Screens/Footer/ScreenFooter.cs | 83 +++++++++++++++++-- osu.Game/Screens/Footer/ScreenFooterButton.cs | 10 ++- 3 files changed, 116 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index acdd1db728..fab2fccb35 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,19 +12,20 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Footer; namespace osu.Game.Overlays.Mods { /// - /// A sheared overlay which provides a header and footer and basic animations. - /// Exposes , and as valid targets for content. + /// A sheared overlay which provides a header and basic animations. + /// Exposes and as valid targets for content. /// public abstract partial class ShearedOverlayContainer : OsuFocusedOverlayContainer { - protected const float PADDING = 14; + public const float PADDING = 14; [Cached] - protected readonly OverlayColourProvider ColourProvider; + public readonly OverlayColourProvider ColourProvider; /// /// The overlay's header. @@ -35,6 +37,13 @@ namespace osu.Game.Overlays.Mods /// protected Container Footer { get; private set; } + [Resolved(canBeNull: true)] + [CanBeNull] + private ScreenFooter footer { get; set; } + + // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. + public virtual bool UseNewFooter => false; + /// /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. @@ -65,7 +74,7 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load() { - const float footer_height = 50; + const float footer_height = ScreenFooter.HEIGHT; Child = TopLevelContent = new Container { @@ -113,6 +122,11 @@ namespace osu.Game.Overlays.Mods }; } + /// + /// Creates content to be displayed on the game-wide footer. + /// + public virtual Drawable CreateFooterContent() => Empty(); + protected override bool OnClick(ClickEvent e) { if (State.Value == Visibility.Visible) @@ -131,7 +145,13 @@ namespace osu.Game.Overlays.Mods this.FadeIn(fade_in_duration, Easing.OutQuint); Header.MoveToY(0, fade_in_duration, Easing.OutQuint); - Footer.MoveToY(0, fade_in_duration, Easing.OutQuint); + + if (UseNewFooter && footer != null) + { + footer.SetOverlayContent(this); + } + else + Footer.MoveToY(0, fade_in_duration, Easing.OutQuint); } protected override void PopOut() @@ -142,7 +162,13 @@ namespace osu.Game.Overlays.Mods this.FadeOut(fade_out_duration, Easing.OutQuint); Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint); - Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint); + + if (UseNewFooter && footer != null) + { + footer.ClearOverlayContent(); + } + else + Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index c7090ba344..7779b6a4e5 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -115,8 +115,11 @@ namespace osu.Game.Screens.Footer public void SetButtons(IReadOnlyList buttons) { + temporarilyHiddenButtons.Clear(); overlays.Clear(); + ClearOverlayContent(); + var oldButtons = buttonsFlow.ToArray(); for (int i = 0; i < oldButtons.Length; i++) @@ -127,9 +130,9 @@ namespace osu.Game.Screens.Footer removedButtonsContainer.Add(oldButton); if (buttons.Count > 0) - makeButtonDisappearToRightAndExpire(oldButton, i, oldButtons.Length); + makeButtonDisappearToRight(oldButton, i, oldButtons.Length, true); else - makeButtonDisappearToBottomAndExpire(oldButton, i, oldButtons.Length); + makeButtonDisappearToBottom(oldButton, i, oldButtons.Length, true); } for (int i = 0; i < buttons.Count; i++) @@ -158,17 +161,85 @@ namespace osu.Game.Screens.Footer } } + private ShearedOverlayContainer? activeOverlay; + private Container? contentContainer; + private readonly List temporarilyHiddenButtons = new List(); + + public void SetOverlayContent(ShearedOverlayContainer overlay) + { + if (contentContainer != null) + { + throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " + + $@"The previous overlay whose content is {contentContainer.Child.GetType().Name} should be hidden first."); + } + + activeOverlay = overlay; + + Debug.Assert(temporarilyHiddenButtons.Count == 0); + + var targetButton = buttonsFlow.SingleOrDefault(b => b.Overlay == overlay); + + temporarilyHiddenButtons.AddRange(targetButton != null + ? buttonsFlow.SkipWhile(b => b != targetButton).Skip(1) + : buttonsFlow); + + for (int i = 0; i < temporarilyHiddenButtons.Count; i++) + makeButtonDisappearToBottom(temporarilyHiddenButtons[i], 0, 0, false); + + var fallbackPosition = buttonsFlow.Any() + ? buttonsFlow.ToSpaceOfOtherDrawable(Vector2.Zero, this) + : BackButton.ToSpaceOfOtherDrawable(BackButton.LayoutRectangle.TopRight + new Vector2(5f, 0f), this); + + var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition; + + var content = overlay.CreateFooterContent(); + + Add(contentContainer = new Container + { + Y = -15f, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = targetPosition.X }, + Child = content, + }); + + if (temporarilyHiddenButtons.Count > 0) + this.Delay(60).Schedule(() => content.Show()); + else + content.Show(); + } + + public void ClearOverlayContent() + { + if (contentContainer == null) + return; + + contentContainer.Child.Hide(); + + double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current; + + Container expireTarget = contentContainer; + contentContainer = null; + activeOverlay = null; + + for (int i = 0; i < temporarilyHiddenButtons.Count; i++) + makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0); + + temporarilyHiddenButtons.Clear(); + + expireTarget.Delay(timeUntilRun).Expire(); + } + private void makeButtonAppearFromLeft(ScreenFooterButton button, int index, int count, float startDelay) => button.AppearFromLeft(startDelay + (count - index) * delay_per_button); private void makeButtonAppearFromBottom(ScreenFooterButton button, int index) => button.AppearFromBottom(index * delay_per_button); - private void makeButtonDisappearToRightAndExpire(ScreenFooterButton button, int index, int count) - => button.DisappearToRightAndExpire((count - index) * delay_per_button); + private void makeButtonDisappearToRight(ScreenFooterButton button, int index, int count, bool expire) + => button.DisappearToRight((count - index) * delay_per_button, expire); - private void makeButtonDisappearToBottomAndExpire(ScreenFooterButton button, int index, int count) - => button.DisappearToBottomAndExpire((count - index) * delay_per_button); + private void makeButtonDisappearToBottom(ScreenFooterButton button, int index, int count, bool expire) + => button.DisappearToBottom((count - index) * delay_per_button, expire); private void showOverlay(OverlayContainer overlay) { diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index cd1de2454b..c1dbbb071d 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -243,22 +243,24 @@ namespace osu.Game.Screens.Footer .FadeIn(240, Easing.OutCubic); } - public void DisappearToRightAndExpire(double delay) + public void DisappearToRight(double delay, bool expire) { Content.Delay(delay) .FadeOut(240, Easing.InOutCubic) .MoveToX(300f, 360, Easing.InOutCubic); - this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); + if (expire) + this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); } - public void DisappearToBottomAndExpire(double delay) + public void DisappearToBottom(double delay, bool expire) { Content.Delay(delay) .FadeOut(240, Easing.InOutCubic) .MoveToY(100f, 240, Easing.InOutCubic); - this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); + if (expire) + this.Delay(Content.LatestTransformEndTime - Time.Current).Expire(); } } } From 916d0bfcc26f6de154dc729b5b8cfef0debf6d02 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 08:39:34 +0300 Subject: [PATCH 034/670] Temporarily show screen footer if hidden while overlay is present --- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index fab2fccb35..d3b1b9244b 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -138,6 +138,8 @@ namespace osu.Game.Overlays.Mods return base.OnClick(e); } + private bool hideFooterOnPopOut; + protected override void PopIn() { const double fade_in_duration = 400; @@ -149,6 +151,12 @@ namespace osu.Game.Overlays.Mods if (UseNewFooter && footer != null) { footer.SetOverlayContent(this); + + if (footer.State.Value == Visibility.Hidden) + { + footer.Show(); + hideFooterOnPopOut = true; + } } else Footer.MoveToY(0, fade_in_duration, Easing.OutQuint); @@ -166,6 +174,12 @@ namespace osu.Game.Overlays.Mods if (UseNewFooter && footer != null) { footer.ClearOverlayContent(); + + if (hideFooterOnPopOut) + { + footer.Hide(); + hideFooterOnPopOut = false; + } } else Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint); From 2319fa11ec640f3922590e9f10e2858d6ebb9168 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 08:42:17 +0300 Subject: [PATCH 035/670] Support performing custom overlay-specific action with back button --- .../Overlays/Mods/ShearedOverlayContainer.cs | 6 ++++ osu.Game/Screens/Footer/ScreenFooter.cs | 31 +++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index d3b1b9244b..c9c3c62404 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -127,6 +127,12 @@ namespace osu.Game.Overlays.Mods /// public virtual Drawable CreateFooterContent() => Empty(); + /// + /// Invoked when the back button in the footer is pressed. + /// + /// Whether the back button should not close the overlay. + public virtual bool OnBackButton() => false; + protected override bool OnClick(ClickEvent e) { if (State.Value == Visibility.Visible) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 7779b6a4e5..d6c98d1c64 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Footer private readonly List overlays = new List(); - private ScreenBackButton backButton = null!; + private Box background = null!; private FillFlowContainer buttonsFlow = null!; private Container removedButtonsContainer = null!; private LogoTrackingContainer logoTrackingContainer = null!; @@ -36,6 +36,8 @@ namespace osu.Game.Screens.Footer [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + public ScreenBackButton BackButton { get; private set; } = null!; + public Action? OnBack; public ScreenFooter(BackReceptor? receptor = null) @@ -48,7 +50,7 @@ namespace osu.Game.Screens.Footer if (receptor == null) Add(receptor = new BackReceptor()); - receptor.OnBackPressed = () => backButton.TriggerClick(); + receptor.OnBackPressed = () => BackButton.TriggerClick(); } [BackgroundDependencyLoader] @@ -71,12 +73,12 @@ namespace osu.Game.Screens.Footer Spacing = new Vector2(7, 0), AutoSizeAxes = Axes.Both }, - backButton = new ScreenBackButton + BackButton = new ScreenBackButton { Margin = new MarginPadding { Bottom = 15f, Left = 12f }, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Action = () => OnBack?.Invoke(), + Action = onBackPressed, }, removedButtonsContainer = new Container { @@ -243,13 +245,24 @@ namespace osu.Game.Screens.Footer private void showOverlay(OverlayContainer overlay) { - foreach (var o in overlays) + foreach (var o in overlays.Where(o => o != overlay)) + o.Hide(); + + overlay.ToggleVisibility(); + } + + private void onBackPressed() + { + if (activeOverlay != null) { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); + if (activeOverlay.OnBackButton()) + return; + + activeOverlay.Hide(); + return; } + + OnBack?.Invoke(); } public partial class BackReceptor : Drawable, IKeyBindingHandler From b8816bfc28f2ccc527f6d6ae9650749777cbe637 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 09:59:40 +0300 Subject: [PATCH 036/670] Update colour scheme of footer in line with visible overlay --- osu.Game/Overlays/OverlayColourProvider.cs | 16 +++++++++++++--- osu.Game/Screens/Footer/ScreenFooter.cs | 17 ++++++++++++++++- osu.Game/Screens/Footer/ScreenFooterButton.cs | 10 +++++----- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index a4f6527024..06b42eafc0 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -9,11 +9,11 @@ namespace osu.Game.Overlays { public class OverlayColourProvider { - private readonly OverlayColourScheme colourScheme; + public OverlayColourScheme ColourScheme { get; private set; } public OverlayColourProvider(OverlayColourScheme colourScheme) { - this.colourScheme = colourScheme; + ColourScheme = colourScheme; } // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. @@ -47,7 +47,17 @@ namespace osu.Game.Overlays public Color4 Background5 => getColour(0.1f, 0.15f); public Color4 Background6 => getColour(0.1f, 0.1f); - private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1)); + /// + /// Changes the value of to a different colour scheme. + /// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually. + /// + /// The proposed colour scheme. + public void ChangeColourScheme(OverlayColourScheme colourScheme) + { + ColourScheme = colourScheme; + } + + private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 private static float getBaseHue(OverlayColourScheme colourScheme) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index d6c98d1c64..cef891f8c0 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Screens.Menu; using osuTK; @@ -58,7 +59,7 @@ namespace osu.Game.Screens.Footer { InternalChildren = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background5 @@ -194,6 +195,8 @@ namespace osu.Game.Screens.Footer var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition; + updateColourScheme(overlay.ColourProvider.ColourScheme); + var content = overlay.CreateFooterContent(); Add(contentContainer = new Container @@ -229,6 +232,18 @@ namespace osu.Game.Screens.Footer temporarilyHiddenButtons.Clear(); expireTarget.Delay(timeUntilRun).Expire(); + + updateColourScheme(OverlayColourScheme.Aquamarine); + } + + private void updateColourScheme(OverlayColourScheme colourScheme) + { + colourProvider.ChangeColourScheme(colourScheme); + + background.FadeColour(colourProvider.Background5, 150, Easing.OutQuint); + + foreach (var button in buttonsFlow) + button.UpdateDisplay(); } private void makeButtonAppearFromLeft(ScreenFooterButton button, int index, int count, float startDelay) diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index c1dbbb071d..0be7ef95b5 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -166,8 +166,8 @@ namespace osu.Game.Screens.Footer if (Overlay != null) OverlayState.BindTo(Overlay.State); - OverlayState.BindValueChanged(_ => updateDisplay()); - Enabled.BindValueChanged(_ => updateDisplay(), true); + OverlayState.BindValueChanged(_ => UpdateDisplay()); + Enabled.BindValueChanged(_ => UpdateDisplay(), true); FinishTransforms(true); } @@ -186,11 +186,11 @@ namespace osu.Game.Screens.Footer protected override bool OnHover(HoverEvent e) { - updateDisplay(); + UpdateDisplay(); return true; } - protected override void OnHoverLost(HoverLostEvent e) => updateDisplay(); + protected override void OnHoverLost(HoverLostEvent e) => UpdateDisplay(); public virtual bool OnPressed(KeyBindingPressEvent e) { @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Footer public virtual void OnReleased(KeyBindingReleaseEvent e) { } - private void updateDisplay() + public void UpdateDisplay() { Color4 backgroundColour = OverlayState.Value == Visibility.Visible ? buttonAccentColour : colourProvider.Background3; Color4 textColour = OverlayState.Value == Visibility.Visible ? colourProvider.Background6 : colourProvider.Content1; From fb77260afc617d2e0f3039987e8168a862535bd6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:06:49 +0300 Subject: [PATCH 037/670] Fix footer buttons receiving input while put away from screen --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 0be7ef95b5..b39e1e11c3 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -172,6 +172,9 @@ namespace osu.Game.Screens.Footer FinishTransforms(true); } + // use Content for tracking input as some buttons might be temporarily hidden with DisappearToBottom, and they become hidden by moving Content away from screen. + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + public GlobalAction? Hotkey; protected override bool OnClick(ClickEvent e) From 56d1255f8ae819f6cd74d8fe8f61aa975ac32cde Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 06:15:50 +0300 Subject: [PATCH 038/670] Fix footer button transforms getting interrupted by consecutive method calls --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index b39e1e11c3..a88ba1aead 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -230,6 +230,7 @@ namespace osu.Game.Screens.Footer public void AppearFromLeft(double delay) { + Content.FinishTransforms(); Content.MoveToX(-300f) .FadeOut() .Delay(delay) @@ -239,6 +240,7 @@ namespace osu.Game.Screens.Footer public void AppearFromBottom(double delay) { + Content.FinishTransforms(); Content.MoveToY(100f) .FadeOut() .Delay(delay) @@ -248,6 +250,7 @@ namespace osu.Game.Screens.Footer public void DisappearToRight(double delay, bool expire) { + Content.FinishTransforms(); Content.Delay(delay) .FadeOut(240, Easing.InOutCubic) .MoveToX(300f, 360, Easing.InOutCubic); @@ -258,6 +261,7 @@ namespace osu.Game.Screens.Footer public void DisappearToBottom(double delay, bool expire) { + Content.FinishTransforms(); Content.Delay(delay) .FadeOut(240, Easing.InOutCubic) .MoveToY(100f, 240, Easing.InOutCubic); From 900d15e777fab79d533ea7b31dcfeaac5f410d4a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:19:58 +0300 Subject: [PATCH 039/670] Add test coverage --- .../UserInterface/TestSceneScreenFooter.cs | 176 ++++++++++++++++-- osu.Game/Screens/Footer/ScreenFooterButton.cs | 4 +- 2 files changed, 167 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index dabb2e7f50..70c3664b9a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -2,10 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Screens.Footer; @@ -15,25 +21,31 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene { + private DependencyProvidingContainer contentContainer = null!; private ScreenFooter screenFooter = null!; private TestModSelectOverlay overlay = null!; [SetUp] public void SetUp() => Schedule(() => { - Children = new Drawable[] + screenFooter = new ScreenFooter(); + + Child = contentContainer = new DependencyProvidingContainer { - overlay = new TestModSelectOverlay + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { - Padding = new MarginPadding - { - Bottom = ScreenFooter.HEIGHT - } + (typeof(ScreenFooter), screenFooter) }, - new PopoverContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = screenFooter = new ScreenFooter(), + overlay = new TestModSelectOverlay(), + new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue, + Child = screenFooter, + }, }, }; @@ -82,14 +94,156 @@ namespace osu.Game.Tests.Visual.UserInterface })); } + [Test] + public void TestExternalOverlayContent() + { + TestShearedOverlayContainer externalOverlay = null!; + + AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer())); + AddStep("set buttons", () => screenFooter.SetButtons(new[] + { + new ScreenFooterButton(externalOverlay) + { + AccentColour = Dependencies.Get().Orange1, + Icon = FontAwesome.Solid.Toolbox, + Text = "One", + }, + new ScreenFooterButton { Text = "Two", Action = () => { } }, + new ScreenFooterButton { Text = "Three", Action = () => { } }, + })); + AddWaitStep("wait for transition", 3); + + AddStep("show overlay", () => externalOverlay.Show()); + AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType().Single().IsPresent); + AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType().Skip(1).All(b => b.Child.Parent!.Y > 0)); + + AddStep("hide overlay", () => externalOverlay.Hide()); + AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType().SingleOrDefault()?.IsPresent != true); + AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType().Skip(1).All(b => b.ChildrenOfType().First().Y == 0)); + } + + [Test] + public void TestTemporarilyShowFooter() + { + TestShearedOverlayContainer externalOverlay = null!; + + AddStep("hide footer", () => screenFooter.Hide()); + AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty())); + + AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer())); + AddStep("show external overlay", () => externalOverlay.Show()); + AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible); + AddAssert("content displayed in footer", () => screenFooter.ChildrenOfType().Single().IsPresent); + + AddStep("hide external overlay", () => externalOverlay.Hide()); + AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); + AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType().SingleOrDefault()?.IsPresent != true); + + AddStep("show footer", () => screenFooter.Show()); + AddAssert("content still hidden from footer", () => screenFooter.ChildrenOfType().SingleOrDefault()?.IsPresent != true); + + AddStep("show external overlay", () => externalOverlay.Show()); + AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible); + + AddStep("hide external overlay", () => externalOverlay.Hide()); + AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible); + + AddStep("hide footer", () => screenFooter.Hide()); + AddStep("show external overlay", () => externalOverlay.Show()); + } + + [Test] + public void TestBackButton() + { + TestShearedOverlayContainer externalOverlay = null!; + + AddStep("hide footer", () => screenFooter.Hide()); + AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty())); + + AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer())); + AddStep("show external overlay", () => externalOverlay.Show()); + AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible); + + AddStep("press back", () => this.ChildrenOfType().Single().TriggerClick()); + AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden); + AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); + + AddStep("show external overlay", () => externalOverlay.Show()); + AddStep("set block count", () => externalOverlay.BackButtonCount = 1); + AddStep("press back", () => this.ChildrenOfType().Single().TriggerClick()); + AddAssert("overlay still visible", () => externalOverlay.State.Value == Visibility.Visible); + AddAssert("footer still shown", () => screenFooter.State.Value == Visibility.Visible); + AddStep("press back again", () => this.ChildrenOfType().Single().TriggerClick()); + AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden); + AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); + } + private partial class TestModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; + } - public TestModSelectOverlay() - : base(OverlayColourScheme.Aquamarine) + private partial class TestShearedOverlayContainer : ShearedOverlayContainer + { + public override bool UseNewFooter => true; + + public TestShearedOverlayContainer() + : base(OverlayColourScheme.Orange) { } + + [BackgroundDependencyLoader] + private void load() + { + Header.Title = "Test overlay"; + Header.Description = "An overlay that is made purely for testing purposes."; + } + + public int BackButtonCount; + + public override bool OnBackButton() + { + if (BackButtonCount > 0) + { + BackButtonCount--; + return true; + } + + return false; + } + + public override Drawable CreateFooterContent() => new TestFooterContent(); + + public partial class TestFooterContent : VisibilityContainer + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new[] + { + new ShearedButton(200) { Text = "Action #1", Action = () => { } }, + new ShearedButton(140) { Text = "Action #2", Action = () => { } }, + } + }; + } + + protected override void PopIn() + { + this.MoveToY(0, 400, Easing.OutQuint) + .FadeIn(400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.MoveToY(-20f, 200, Easing.OutQuint) + .FadeOut(200, Easing.OutQuint); + } + } } } } diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index a88ba1aead..6515203ca0 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Footer private Colour4 buttonAccentColour; - protected Colour4 AccentColour + public Colour4 AccentColour { set { @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Footer } } - protected IconUsage Icon + public IconUsage Icon { set => icon.Icon = value; } From 467d7c4f54fc833be0d92bc941db11224155b787 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:17:40 +0300 Subject: [PATCH 040/670] Refactor game-wide layout order of footer to fix depth issues with overlays and improve UX With this new order, the logo can be easily moved to display in front of the footer in `SongSelectV2` without breaking experience when footer-based overlays are present. Such overlays (i.e. mod select overlay) will also be dimmed alongside the current screen when a game-wide overlay is open (e.g. settings). --- .../SongSelect/TestSceneSongSelectV2.cs | 6 --- osu.Game/OsuGame.cs | 31 ++++++++++---- .../Overlays/Mods/ShearedOverlayContainer.cs | 24 +++++++---- osu.Game/Screens/Footer/ScreenFooter.cs | 33 ++++++++++++--- osu.Game/Screens/SelectV2/SongSelectV2.cs | 41 ++++++++----------- 5 files changed, 82 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs index 674eaa2ff8..0a632793cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs @@ -181,12 +181,6 @@ namespace osu.Game.Tests.Visual.SongSelect #endregion - protected override void Update() - { - base.Update(); - Stack.Padding = new MarginPadding { Bottom = screenScreenFooter.DrawHeight - screenScreenFooter.Y }; - } - private void updateFooter(IScreen? _, IScreen? newScreen) { if (newScreen is IOsuScreen osuScreen && osuScreen.ShowFooter) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index cf32daab00..3862fea0e2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -51,6 +51,7 @@ using osu.Game.Online.Chat; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapListing; +using osu.Game.Overlays.Mods; using osu.Game.Overlays.Music; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.SkinEditor; @@ -132,8 +133,12 @@ namespace osu.Game private Container topMostOverlayContent; + private Container footerBasedOverlayContent; + protected ScalingContainer ScreenContainer { get; private set; } + private Container logoContainer; + protected Container ScreenOffsetContainer { get; private set; } private Container overlayOffsetContainer; @@ -156,8 +161,6 @@ namespace osu.Game private float toolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0); - private float screenFooterOffset => (ScreenFooter?.DrawHeight ?? 0) - (ScreenFooter?.Position.Y ?? 0); - private IdleTracker idleTracker; /// @@ -242,7 +245,11 @@ namespace osu.Game throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once."); externalOverlays.Add(overlayContainer); - overlayContent.Add(overlayContainer); + + if (overlayContainer is ShearedOverlayContainer) + footerBasedOverlayContent.Add(overlayContainer); + else + overlayContent.Add(overlayContainer); if (overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer) focusedOverlays.Add(focusedOverlayContainer); @@ -290,6 +297,8 @@ namespace osu.Game if (hideToolbar) Toolbar.Hide(); } + public void ChangeLogoDepth(bool inFrontOfFooter) => ScreenContainer.ChangeChildDepth(logoContainer, inFrontOfFooter ? float.MinValue : 0); + protected override UserInputManager CreateUserInputManager() { var userInputManager = base.CreateUserInputManager(); @@ -934,7 +943,6 @@ namespace osu.Game return string.Join(" / ", combinations); }; - Container logoContainer; ScreenFooter.BackReceptor backReceptor; dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); @@ -976,8 +984,15 @@ namespace osu.Game Origin = Anchor.BottomLeft, Action = () => ScreenFooter.OnBack?.Invoke(), }, + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + footerBasedOverlayContent = new Container + { + Depth = -1, + RelativeSizeAxes = Axes.Both, + }, new PopoverContainer { + Depth = -1, RelativeSizeAxes = Axes.Both, Child = ScreenFooter = new ScreenFooter(backReceptor) { @@ -991,7 +1006,6 @@ namespace osu.Game } }, }, - logoContainer = new Container { RelativeSizeAxes = Axes.Both }, } }, } @@ -1025,7 +1039,7 @@ namespace osu.Game if (!IsDeployedBuild) { - dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); + dependencies.Cache(versionManager = new VersionManager()); loadComponentSingleFile(versionManager, ScreenContainer.Add); } @@ -1072,7 +1086,7 @@ namespace osu.Game loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements - loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), overlayContent.Add, true); + loadComponentSingleFile(FirstRunOverlay = new FirstRunSetupOverlay(), footerBasedOverlayContent.Add, true); loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true); loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); @@ -1137,7 +1151,7 @@ namespace osu.Game } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { FirstRunOverlay, chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, news, dashboard, beatmapListing, changelogOverlay, rankingsOverlay, wikiOverlay }; foreach (var overlay in singleDisplayOverlays) { @@ -1485,7 +1499,6 @@ namespace osu.Game ScreenOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; overlayOffsetContainer.Padding = new MarginPadding { Top = toolbarOffset }; - ScreenStack.Padding = new MarginPadding { Bottom = screenFooterOffset }; float horizontalOffset = 0f; diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index c9c3c62404..b5435e7e58 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -30,16 +28,15 @@ namespace osu.Game.Overlays.Mods /// /// The overlay's header. /// - protected ShearedOverlayHeader Header { get; private set; } + protected ShearedOverlayHeader Header { get; private set; } = null!; /// /// The overlay's footer. /// protected Container Footer { get; private set; } - [Resolved(canBeNull: true)] - [CanBeNull] - private ScreenFooter footer { get; set; } + [Resolved] + private ScreenFooter? footer { get; set; } // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. public virtual bool UseNewFooter => false; @@ -48,12 +45,12 @@ namespace osu.Game.Overlays.Mods /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. /// - protected Container TopLevelContent { get; private set; } + protected Container TopLevelContent { get; private set; } = null!; /// /// A container for content that is to be displayed between the header and footer. /// - protected Container MainAreaContent { get; private set; } + protected Container MainAreaContent { get; private set; } = null!; /// /// A container for content that is to be displayed inside the footer. @@ -64,6 +61,10 @@ namespace osu.Game.Overlays.Mods protected override bool BlockNonPositionalInput => true; + // ShearedOverlayContainers are placed at a layer within the screen container as they rely on ScreenFooter which must be placed there. + // Therefore, dimming must be managed locally, since DimMainContent dims the entire screen layer. + protected sealed override bool DimMainContent => false; + protected ShearedOverlayContainer(OverlayColourScheme colourScheme) { RelativeSizeAxes = Axes.Both; @@ -81,6 +82,11 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background6.Opacity(0.75f), + }, Header = new ShearedOverlayHeader { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index cef891f8c0..dcf64e9291 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -24,6 +25,7 @@ namespace osu.Game.Screens.Footer { private const int padding = 60; private const float delay_per_button = 30; + private const double transition_duration = 400; public const int HEIGHT = 50; @@ -37,6 +39,9 @@ namespace osu.Game.Screens.Footer [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Resolved] + private OsuGame? game { get; set; } + public ScreenBackButton BackButton { get; private set; } = null!; public Action? OnBack; @@ -101,19 +106,35 @@ namespace osu.Game.Screens.Footer }; } - public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None) => logoTrackingContainer.StartTracking(logo, duration, easing); - public void StopTrackingLogo() => logoTrackingContainer.StopTracking(); + private ScheduledDelegate? changeLogoDepthDelegate; + + public void StartTrackingLogo(OsuLogo logo, float duration = 0, Easing easing = Easing.None) + { + changeLogoDepthDelegate?.Cancel(); + changeLogoDepthDelegate = null; + + logoTrackingContainer.StartTracking(logo, duration, easing); + game?.ChangeLogoDepth(inFrontOfFooter: true); + } + + public void StopTrackingLogo() + { + logoTrackingContainer.StopTracking(); + + if (game != null) + changeLogoDepthDelegate = Scheduler.AddDelayed(() => game.ChangeLogoDepth(inFrontOfFooter: false), transition_duration); + } protected override void PopIn() { - this.MoveToY(0, 400, Easing.OutQuint) - .FadeIn(400, Easing.OutQuint); + this.MoveToY(0, transition_duration, Easing.OutQuint) + .FadeIn(transition_duration, Easing.OutQuint); } protected override void PopOut() { - this.MoveToY(HEIGHT, 400, Easing.OutQuint) - .FadeOut(400, Easing.OutQuint); + this.MoveToY(HEIGHT, transition_duration, Easing.OutQuint) + .FadeOut(transition_duration, Easing.OutQuint); } public void SetButtons(IReadOnlyList buttons) diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index 10ed7783c4..a8730ad808 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Screens; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -23,6 +22,8 @@ namespace osu.Game.Screens.SelectV2 /// public partial class SongSelectV2 : OsuScreen { + private const float logo_scale = 0.4f; + private readonly ModSelectOverlay modSelectOverlay = new SoloModSelectOverlay(); [Cached] @@ -30,15 +31,14 @@ namespace osu.Game.Screens.SelectV2 public override bool ShowFooter => true; + [Resolved] + private OsuLogo? logo { get; set; } + [BackgroundDependencyLoader] private void load() { AddRangeInternal(new Drawable[] { - new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - }, modSelectOverlay, }); } @@ -50,6 +50,17 @@ namespace osu.Game.Screens.SelectV2 new ScreenFooterButtonOptions(), }; + protected override void LoadComplete() + { + base.LoadComplete(); + + modSelectOverlay.State.BindValueChanged(v => + { + logo?.ScaleTo(v.NewValue == Visibility.Visible ? 0f : logo_scale, 400, Easing.OutQuint) + .FadeTo(v.NewValue == Visibility.Visible ? 0f : 1f, 200, Easing.OutQuint); + }, true); + } + public override void OnEntering(ScreenTransitionEvent e) { this.FadeIn(); @@ -74,17 +85,6 @@ namespace osu.Game.Screens.SelectV2 return base.OnExiting(e); } - public override bool OnBackButton() - { - if (modSelectOverlay.State.Value == Visibility.Visible) - { - modSelectOverlay.Hide(); - return true; - } - - return false; - } - protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -99,7 +99,7 @@ namespace osu.Game.Screens.SelectV2 } logo.FadeIn(240, Easing.OutQuint); - logo.ScaleTo(0.4f, 240, Easing.OutQuint); + logo.ScaleTo(logo_scale, 240, Easing.OutQuint); logo.Action = () => { @@ -122,14 +122,9 @@ namespace osu.Game.Screens.SelectV2 logo.FadeOut(120, Easing.Out); } - private partial class SoloModSelectOverlay : ModSelectOverlay + private partial class SoloModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; - - public SoloModSelectOverlay() - : base(OverlayColourScheme.Aquamarine) - { - } } private partial class PlayerLoaderV2 : PlayerLoader From 48bf3f1385e6b99fd5bd0e8104d40487b22c781a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 08:38:43 +0300 Subject: [PATCH 041/670] Migrate mod select overlay footer content --- .../TestSceneModSelectOverlay.cs | 39 ++-- .../Overlays/Mods/ModSelectFooterContent.cs | 177 ++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 165 ++-------------- .../OnlinePlay/FreeModSelectOverlay.cs | 41 +++- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- .../Tests/Visual/Gameplay/ScoringTestScene.cs | 1 - 7 files changed, 257 insertions(+), 170 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectFooterContent.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dedad3a40a..8b79320ffb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Screens.Footer; using osu.Game.Tests.Mods; using osuTK; using osuTK.Input; @@ -93,12 +94,28 @@ namespace osu.Game.Tests.Visual.UserInterface private void createScreen() { - AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay + AddStep("create screen", () => { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - Beatmap = Beatmap.Value, - SelectedMods = { BindTarget = SelectedMods } + var receptor = new ScreenFooter.BackReceptor(); + var footer = new ScreenFooter(receptor); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) }, + Children = new Drawable[] + { + receptor, + modSelectOverlay = new TestModSelectOverlay + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Beatmap = { Value = Beatmap.Value }, + SelectedMods = { BindTarget = SelectedMods }, + }, + footer, + } + }; }); waitForColumnLoad(); } @@ -119,7 +136,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); + return Precision.AlmostEquals(multiplier, this.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -134,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); + return Precision.AlmostEquals(multiplier, this.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -756,7 +773,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("click back button", () => { - InputManager.MoveMouseTo(modSelectOverlay.BackButton); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden); @@ -884,7 +901,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddAssert("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); + () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -894,7 +911,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); + () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } [Test] @@ -1014,8 +1031,6 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; - - public new ShearedButton BackButton => base.BackButton; } private class TestUnimplementedMod : Mod diff --git a/osu.Game/Overlays/Mods/ModSelectFooterContent.cs b/osu.Game/Overlays/Mods/ModSelectFooterContent.cs new file mode 100644 index 0000000000..146b8e4ebe --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectFooterContent.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public partial class ModSelectFooterContent : VisibilityContainer + { + private readonly ModSelectOverlay overlay; + + private RankingInformationDisplay? rankingInformationDisplay; + private BeatmapAttributesDisplay? beatmapAttributesDisplay; + private FillFlowContainer buttonFlow = null!; + private FillFlowContainer contentFlow = null!; + + public DeselectAllModsButton? DeselectAllModsButton { get; set; } + + public readonly IBindable Beatmap = new Bindable(); + public readonly IBindable> ActiveMods = new Bindable>(); + + /// + /// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown. + /// + protected virtual bool ShowModEffects => true; + + /// + /// Whether the ranking information and beatmap attributes displays are stacked vertically due to small space. + /// + public bool DisplaysStackedVertically { get; private set; } + + public ModSelectFooterContent(ModSelectOverlay overlay) + { + this.overlay = overlay; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = buttonFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding { Horizontal = 20 }, + Spacing = new Vector2(10), + ChildrenEnumerable = CreateButtons(), + }; + + if (ShowModEffects) + { + AddInternal(contentFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 10), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Horizontal = 20 }, + Children = new Drawable[] + { + rankingInformationDisplay = new RankingInformationDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight + }, + beatmapAttributesDisplay = new BeatmapAttributesDisplay + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + BeatmapInfo = { Value = Beatmap.Value?.BeatmapInfo }, + }, + } + }); + } + } + + private ModSettingChangeTracker? modSettingChangeTracker; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.BindValueChanged(b => + { + if (beatmapAttributesDisplay != null) + beatmapAttributesDisplay.BeatmapInfo.Value = b.NewValue?.BeatmapInfo; + }, true); + + ActiveMods.BindValueChanged(m => + { + updateInformation(); + + modSettingChangeTracker?.Dispose(); + + // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can + // potentially be stale, due to complexities in the way change trackers work. + // + // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 + modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value); + modSettingChangeTracker.SettingChanged += _ => updateInformation(); + }, true); + } + + private void updateInformation() + { + if (rankingInformationDisplay != null) + { + double multiplier = 1.0; + + foreach (var mod in ActiveMods.Value) + multiplier *= mod.ScoreMultiplier; + + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked); + } + + if (beatmapAttributesDisplay != null) + beatmapAttributesDisplay.Mods.Value = ActiveMods.Value; + } + + protected override void Update() + { + base.Update(); + + if (beatmapAttributesDisplay != null) + { + float rightEdgeOfLastButton = buttonFlow[^1].ScreenSpaceDrawQuad.TopRight.X; + + // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. + // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. + float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = buttonFlow.ToScreenSpace(buttonFlow.DrawSize - new Vector2(640, 0)).X; + + DisplaysStackedVertically = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay; + + // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. + if (Alpha == 1) + beatmapAttributesDisplay.Collapsed.Value = DisplaysStackedVertically; + + contentFlow.LayoutDuration = 200; + contentFlow.LayoutEasing = Easing.OutQuint; + contentFlow.Direction = DisplaysStackedVertically ? FillDirection.Vertical : FillDirection.Horizontal; + } + } + + protected virtual IEnumerable CreateButtons() => new[] + { + DeselectAllModsButton = new DeselectAllModsButton(overlay) + }; + + protected override void PopIn() + { + this.MoveToY(0, 400, Easing.OutQuint) + .FadeIn(400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.MoveToY(-20f, 200, Easing.OutQuint) + .FadeOut(200, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b0d58480db..40be4e08a6 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -27,6 +27,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Footer; using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -87,11 +88,6 @@ namespace osu.Game.Overlays.Mods public ShearedSearchTextBox SearchTextBox { get; private set; } = null!; - /// - /// Whether the effects (on score multiplier, on or beatmap difficulty) of the current selected set of mods should be shown. - /// - protected virtual bool ShowModEffects => true; - /// /// Whether per-mod customisation controls are visible. /// @@ -108,11 +104,6 @@ namespace osu.Game.Overlays.Mods protected virtual IReadOnlyList ComputeActiveMods() => SelectedMods.Value; - protected virtual IEnumerable CreateFooterButtons() - { - yield return deselectAllModsButton = new DeselectAllModsButton(this); - } - private readonly Bindable>> globalAvailableMods = new Bindable>>(); public IEnumerable AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); @@ -121,34 +112,18 @@ namespace osu.Game.Overlays.Mods private ColumnScrollContainer columnScroll = null!; private ColumnFlowContainer columnFlow = null!; - private FillFlowContainer footerButtonFlow = null!; - private FillFlowContainer footerContentFlow = null!; - private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private RankingInformationDisplay? rankingInformationDisplay; - private BeatmapAttributesDisplay? beatmapAttributesDisplay; private ModCustomisationPanel customisationPanel = null!; - protected ShearedButton BackButton { get; private set; } = null!; - protected SelectAllModsButton? SelectAllModsButton { get; set; } + protected virtual SelectAllModsButton? SelectAllModsButton => null; private Sample? columnAppearSample; - private WorkingBeatmap? beatmap; + public readonly Bindable Beatmap = new Bindable(); - public WorkingBeatmap? Beatmap - { - get => beatmap; - set - { - if (beatmap == value) return; - - beatmap = value; - if (IsLoaded && beatmapAttributesDisplay != null) - beatmapAttributesDisplay.BeatmapInfo.Value = beatmap?.BeatmapInfo; - } - } + [Resolved] + private ScreenFooter? footer { get; set; } protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) @@ -226,59 +201,6 @@ namespace osu.Game.Overlays.Mods } }); - FooterContent.Add(footerButtonFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Vertical = PADDING, - Horizontal = 70 - }, - Spacing = new Vector2(10), - ChildrenEnumerable = CreateFooterButtons().Prepend(BackButton = new ShearedButton(BUTTON_WIDTH) - { - Text = CommonStrings.Back, - Action = Hide, - DarkerColour = colours.Pink2, - LighterColour = colours.Pink1 - }) - }); - - if (ShowModEffects) - { - FooterContent.Add(footerContentFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 10), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding - { - Vertical = PADDING, - Horizontal = 20 - }, - Children = new Drawable[] - { - rankingInformationDisplay = new RankingInformationDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight - }, - beatmapAttributesDisplay = new BeatmapAttributesDisplay - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - BeatmapInfo = { Value = Beatmap?.BeatmapInfo }, - }, - } - }); - } - globalAvailableMods.BindTo(game.AvailableMods); textSearchStartsActive = configManager.GetBindable(OsuSetting.ModSelectTextSearchStartsActive); @@ -292,8 +214,6 @@ namespace osu.Game.Overlays.Mods SearchTextBox.Current.Value = string.Empty; } - private ModSettingChangeTracker? modSettingChangeTracker; - protected override void LoadComplete() { // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. @@ -316,23 +236,6 @@ namespace osu.Game.Overlays.Mods ActiveMods.Value = ComputeActiveMods(); }, true); - ActiveMods.BindValueChanged(_ => - { - updateOverlayInformation(); - - modSettingChangeTracker?.Dispose(); - - if (AllowCustomisation) - { - // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can - // potentially be stale, due to complexities in the way change trackers work. - // - // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 - modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation(); - } - }, true); - customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => @@ -350,6 +253,16 @@ namespace osu.Game.Overlays.Mods }); } + private ModSelectFooterContent? currentFooterContent; + + public override bool UseNewFooter => true; + + public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + { + Beatmap = { BindTarget = Beatmap }, + ActiveMods = { BindTarget = ActiveMods }, + }; + private static readonly LocalisableString input_search_placeholder = Resources.Localisation.Web.CommonStrings.InputSearch; private static readonly LocalisableString tab_to_search_placeholder = ModSelectOverlayStrings.TabToSearch; @@ -358,26 +271,7 @@ namespace osu.Game.Overlays.Mods base.Update(); SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; - - if (beatmapAttributesDisplay != null) - { - float rightEdgeOfLastButton = footerButtonFlow[^1].ScreenSpaceDrawQuad.TopRight.X; - - // this is cheating a bit; the 640 value is hardcoded based on how wide the expanded panel _generally_ is. - // due to the transition applied, the raw screenspace quad of the panel cannot be used, as it will trigger an ugly feedback cycle of expanding and collapsing. - float projectedLeftEdgeOfExpandedBeatmapAttributesDisplay = footerButtonFlow.ToScreenSpace(footerButtonFlow.DrawSize - new Vector2(640, 0)).X; - - bool screenIsntWideEnough = rightEdgeOfLastButton > projectedLeftEdgeOfExpandedBeatmapAttributesDisplay; - - // only update preview panel's collapsed state after we are fully visible, to ensure all the buttons are where we expect them to be. - if (Alpha == 1) - beatmapAttributesDisplay.Collapsed.Value = screenIsntWideEnough; - - footerContentFlow.LayoutDuration = 200; - footerContentFlow.LayoutEasing = Easing.OutQuint; - footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal; - aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f }; - } + aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = currentFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; } /// @@ -455,27 +349,6 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - /// - /// Updates any information displayed on the overlay regarding the effects of the active mods. - /// This reads from instead of . - /// - private void updateOverlayInformation() - { - if (rankingInformationDisplay != null) - { - double multiplier = 1.0; - - foreach (var mod in ActiveMods.Value) - multiplier *= mod.ScoreMultiplier; - - rankingInformationDisplay.ModMultiplier.Value = multiplier; - rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked); - } - - if (beatmapAttributesDisplay != null) - beatmapAttributesDisplay.Mods.Value = ActiveMods.Value; - } - private void updateCustomisation() { if (!AllowCustomisation) @@ -701,7 +574,7 @@ namespace osu.Game.Overlays.Mods { if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) { - deselectAllModsButton.TriggerClick(); + currentFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; } @@ -732,7 +605,7 @@ namespace osu.Game.Overlays.Mods return base.OnPressed(e); - void hideOverlay() => BackButton.TriggerClick(); + void hideOverlay() => footer?.BackButton.TriggerClick(); } /// @@ -740,7 +613,7 @@ namespace osu.Game.Overlays.Mods /// This is handled locally here due to conflicts in input handling between the search text box and the select all mods button. /// Attempting to handle this action locally in both places leads to a possible scenario /// wherein activating the "select all" platform binding will both select all text in the search box and select all mods. - /// > + /// public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat || e.Action != PlatformAction.SelectAll || SelectAllModsButton == null) diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 7f090aca57..0ed45161f2 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -14,8 +14,6 @@ namespace osu.Game.Screens.OnlinePlay { public partial class FreeModSelectOverlay : ModSelectOverlay { - protected override bool ShowModEffects => false; - protected override bool AllowCustomisation => false; public new Func IsValidMod @@ -24,6 +22,10 @@ namespace osu.Game.Screens.OnlinePlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } + private FreeModSelectFooterContent? currentFooterContent; + + protected override SelectAllModsButton? SelectAllModsButton => currentFooterContent?.SelectAllModsButton; + public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -32,12 +34,33 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - protected override IEnumerable CreateFooterButtons() - => base.CreateFooterButtons() - .Prepend(SelectAllModsButton = new SelectAllModsButton(this) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); + public override Drawable CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + { + Beatmap = { BindTarget = Beatmap }, + ActiveMods = { BindTarget = ActiveMods }, + }; + + private partial class FreeModSelectFooterContent : ModSelectFooterContent + { + private readonly FreeModSelectOverlay overlay; + + protected override bool ShowModEffects => false; + + public SelectAllModsButton? SelectAllModsButton; + + public FreeModSelectFooterContent(FreeModSelectOverlay overlay) + : base(overlay) + { + this.overlay = overlay; + } + + protected override IEnumerable CreateButtons() + => base.CreateButtons() + .Prepend(SelectAllModsButton = new SelectAllModsButton(overlay) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4eb092d08b..78ab8cfa6c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -453,7 +453,7 @@ namespace osu.Game.Screens.OnlinePlay.Match // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID); - UserModsSelectOverlay.Beatmap = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); + UserModsSelectOverlay.Beatmap.Value = Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } protected virtual void UpdateMods() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index df7eabfd21..ecf8210002 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -851,7 +851,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Beatmap = beatmap; - ModSelect.Beatmap = beatmap; + ModSelect.Beatmap.Value = beatmap; advancedStats.BeatmapInfo = beatmap.BeatmapInfo; diff --git a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs index e7053e4202..6908f7f1b4 100644 --- a/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs +++ b/osu.Game/Tests/Visual/Gameplay/ScoringTestScene.cs @@ -658,7 +658,6 @@ namespace osu.Game.Tests.Visual.Gameplay private partial class TestModSelectOverlay : UserModSelectOverlay { - protected override bool ShowModEffects => true; protected override bool ShowPresets => false; public TestModSelectOverlay() From 5dd822ea3899eafde0b2beee191243e65378928a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 09:39:09 +0300 Subject: [PATCH 042/670] Migrate first-run setup overlay footer content --- .../TestSceneFirstRunSetupOverlay.cs | 62 ++++--- .../Overlays/FirstRunSetup/ScreenUIScale.cs | 8 +- osu.Game/Overlays/FirstRunSetupOverlay.cs | 169 +++++++++--------- 3 files changed, 129 insertions(+), 110 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 51da4d8755..2ca06bf2f4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -11,6 +11,8 @@ using Moq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; @@ -20,6 +22,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.Notifications; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osuTK; using osuTK.Input; @@ -28,6 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene { private FirstRunSetupOverlay overlay; + private ScreenFooter footer; private readonly Mock performer = new Mock(); @@ -60,19 +64,16 @@ namespace osu.Game.Tests.Visual.UserInterface .Callback((Notification n) => lastNotification = n); }); - AddStep("add overlay", () => - { - Child = overlay = new FirstRunSetupOverlay - { - State = { Value = Visibility.Visible } - }; - }); + createOverlay(); + + AddStep("show overlay", () => overlay.Show()); } [Test] public void TestBasic() { AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible); + AddAssert("footer visible", () => footer.State.Value == Visibility.Visible); } [Test] @@ -82,16 +83,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("step through", () => { - if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.TriggerClick(); + if (overlay.CurrentScreen?.IsLoaded != false) overlay.NextButton.AsNonNull().TriggerClick(); return overlay.State.Value == Visibility.Hidden; }); AddAssert("first run false", () => !LocalConfig.Get(OsuSetting.ShowFirstRunSetup)); - AddStep("add overlay", () => - { - Child = overlay = new FirstRunSetupOverlay(); - }); + createOverlay(); AddWaitStep("wait some", 5); @@ -109,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (keyboard) InputManager.Key(Key.Enter); else - overlay.NextButton.TriggerClick(); + overlay.NextButton.AsNonNull().TriggerClick(); } return overlay.State.Value == Visibility.Hidden; @@ -128,11 +126,9 @@ namespace osu.Game.Tests.Visual.UserInterface [TestCase(true)] public void TestBackButton(bool keyboard) { - AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value); - AddUntilStep("step to last", () => { - var nextButton = overlay.NextButton; + var nextButton = overlay.NextButton.AsNonNull(); if (overlay.CurrentScreen?.IsLoaded != false) nextButton.TriggerClick(); @@ -142,24 +138,29 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("step back to start", () => { - if (overlay.CurrentScreen?.IsLoaded != false) + if (overlay.CurrentScreen?.IsLoaded != false && !(overlay.CurrentScreen is ScreenWelcome)) { if (keyboard) InputManager.Key(Key.Escape); else - overlay.BackButton.TriggerClick(); + footer.BackButton.TriggerClick(); } return overlay.CurrentScreen is ScreenWelcome; }); - AddAssert("back button disabled", () => !overlay.BackButton.Enabled.Value); + AddAssert("overlay not dismissed", () => overlay.State.Value == Visibility.Visible); if (keyboard) { AddStep("exit via keyboard", () => InputManager.Key(Key.Escape)); AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden); } + else + { + AddStep("press back button", () => footer.BackButton.TriggerClick()); + AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden); + } } [Test] @@ -185,7 +186,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestResumeViaNotification() { - AddStep("step to next", () => overlay.NextButton.TriggerClick()); + AddStep("step to next", () => overlay.NextButton.AsNonNull().TriggerClick()); AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale); @@ -200,6 +201,27 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale); } + private void createOverlay() + { + AddStep("add overlay", () => + { + var receptor = new ScreenFooter.BackReceptor(); + footer = new ScreenFooter(receptor); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) }, + Children = new Drawable[] + { + receptor, + overlay = new FirstRunSetupOverlay(), + footer, + } + }; + }); + } + // interface mocks break hot reload, mocking this stub implementation instead works around it. // see: https://github.com/moq/moq4/issues/1252 [UsedImplicitly] diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 02f0ad9506..d0eefa55c5 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; using osu.Game.Screens.Select; using osu.Game.Tests.Visual; @@ -153,6 +154,7 @@ namespace osu.Game.Overlays.FirstRunSetup OsuScreenStack stack; OsuLogo logo; + ScreenFooter footer; Padding = new MarginPadding(5); @@ -166,7 +168,8 @@ namespace osu.Game.Overlays.FirstRunSetup { RelativePositionAxes = Axes.Both, Position = new Vector2(0.5f), - }) + }), + (typeof(ScreenFooter), footer = new ScreenFooter()), }, RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -178,7 +181,8 @@ namespace osu.Game.Overlays.FirstRunSetup Children = new Drawable[] { stack = new OsuScreenStack(), - logo + footer, + logo, }, }, } diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index f2fdaefbb4..bc11e5d0d2 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -26,6 +26,7 @@ using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Notifications; using osu.Game.Screens; +using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; namespace osu.Game.Overlays @@ -44,8 +45,7 @@ namespace osu.Game.Overlays private ScreenStack? stack; - public ShearedButton NextButton = null!; - public ShearedButton BackButton = null!; + public ShearedButton? NextButton => currentFooterContent?.NextButton; private readonly Bindable showFirstRunSetup = new Bindable(); @@ -90,7 +90,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = 20, }, + Padding = new MarginPadding { Bottom = 20 }, Child = new GridContainer { Anchor = Anchor.Centre, @@ -134,51 +134,6 @@ namespace osu.Game.Overlays } }, }); - - FooterContent.Add(new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Vertical = PADDING }, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new[] - { - Empty(), - BackButton = new ShearedButton(300) - { - Text = CommonStrings.Back, - Action = showPreviousStep, - Enabled = { Value = false }, - DarkerColour = colours.Pink2, - LighterColour = colours.Pink1, - }, - NextButton = new ShearedButton(0) - { - RelativeSizeAxes = Axes.X, - Width = 1, - Text = FirstRunSetupOverlayStrings.GetStarted, - DarkerColour = ColourProvider.Colour2, - LighterColour = ColourProvider.Colour1, - Action = showNextStep - }, - Empty(), - }, - } - }); } protected override void LoadComplete() @@ -190,6 +145,32 @@ namespace osu.Game.Overlays if (showFirstRunSetup.Value) Show(); } + [Resolved] + private ScreenFooter footer { get; set; } = null!; + + private FirstRunSetupFooterContent? currentFooterContent; + + public override bool UseNewFooter => true; + + public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent + { + ShowNextStep = showNextStep, + }; + + public override bool OnBackButton() + { + if (currentStepIndex == 0) + return false; + + Debug.Assert(stack != null); + + stack.CurrentScreen.Exit(); + currentStepIndex--; + + updateButtons(); + return true; + } + public override bool OnPressed(KeyBindingPressEvent e) { if (!e.Repeat) @@ -197,19 +178,12 @@ namespace osu.Game.Overlays switch (e.Action) { case GlobalAction.Select: - NextButton.TriggerClick(); + currentFooterContent?.NextButton.TriggerClick(); return true; case GlobalAction.Back: - if (BackButton.Enabled.Value) - { - BackButton.TriggerClick(); - return true; - } - - // If back button is disabled, we are at the first step. - // The base call will handle dismissal of the overlay. - break; + footer.BackButton.TriggerClick(); + return false; } } @@ -279,19 +253,6 @@ namespace osu.Game.Overlays showNextStep(); } - private void showPreviousStep() - { - if (currentStepIndex == 0) - return; - - Debug.Assert(stack != null); - - stack.CurrentScreen.Exit(); - currentStepIndex--; - - updateButtons(); - } - private void showNextStep() { Debug.Assert(currentStepIndex != null); @@ -322,29 +283,61 @@ namespace osu.Game.Overlays updateButtons(); } - private void updateButtons() + private void updateButtons() => currentFooterContent?.UpdateButtons(currentStepIndex, steps); + + private partial class FirstRunSetupFooterContent : VisibilityContainer { - BackButton.Enabled.Value = currentStepIndex > 0; - NextButton.Enabled.Value = currentStepIndex != null; + public ShearedButton NextButton { get; private set; } = null!; - if (currentStepIndex == null) - return; + public Action? ShowNextStep; - bool isFirstStep = currentStepIndex == 0; - bool isLastStep = currentStepIndex == steps.Count - 1; - - if (isFirstStep) + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - BackButton.Text = CommonStrings.Back; - NextButton.Text = FirstRunSetupOverlayStrings.GetStarted; + RelativeSizeAxes = Axes.Both; + + InternalChild = NextButton = new ShearedButton(0) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Right = 12f }, + RelativeSizeAxes = Axes.X, + Width = 1, + Text = FirstRunSetupOverlayStrings.GetStarted, + DarkerColour = colourProvider.Colour2, + LighterColour = colourProvider.Colour1, + Action = () => ShowNextStep?.Invoke(), + }; } - else - { - BackButton.Text = LocalisableString.Interpolate($@"{CommonStrings.Back} ({steps[currentStepIndex.Value - 1].GetLocalisableDescription()})"); - NextButton.Text = isLastStep - ? CommonStrings.Finish - : LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStepIndex.Value + 1].GetLocalisableDescription()})"); + public void UpdateButtons(int? currentStep, IReadOnlyList steps) + { + NextButton.Enabled.Value = currentStep != null; + + if (currentStep == null) + return; + + bool isFirstStep = currentStep == 0; + bool isLastStep = currentStep == steps.Count - 1; + + if (isFirstStep) + NextButton.Text = FirstRunSetupOverlayStrings.GetStarted; + else + { + NextButton.Text = isLastStep + ? CommonStrings.Finish + : LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStep.Value + 1].GetLocalisableDescription()})"); + } + } + + protected override void PopIn() + { + this.FadeIn(); + } + + protected override void PopOut() + { + this.Delay(400).FadeOut(); } } } From e57a0029f16619ed857ed400f5e3695db2f1d3a9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 04:22:59 +0300 Subject: [PATCH 043/670] Remove local footer from `ShearedOverlayContainer` --- .../UserInterface/TestSceneScreenFooter.cs | 2 - osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 - osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 - .../Overlays/Mods/ShearedOverlayContainer.cs | 47 ++----------------- 4 files changed, 3 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index 70c3664b9a..de2026e538 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -185,8 +185,6 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestShearedOverlayContainer : ShearedOverlayContainer { - public override bool UseNewFooter => true; - public TestShearedOverlayContainer() : base(OverlayColourScheme.Orange) { diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index bc11e5d0d2..47f53a3fa6 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,8 +150,6 @@ namespace osu.Game.Overlays private FirstRunSetupFooterContent? currentFooterContent; - public override bool UseNewFooter => true; - public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent { ShowNextStep = showNextStep, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 40be4e08a6..8e18c39cc2 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -255,8 +255,6 @@ namespace osu.Game.Overlays.Mods private ModSelectFooterContent? currentFooterContent; - public override bool UseNewFooter => true; - public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index b5435e7e58..1ccf274d0d 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Footer; @@ -30,17 +29,9 @@ namespace osu.Game.Overlays.Mods /// protected ShearedOverlayHeader Header { get; private set; } = null!; - /// - /// The overlay's footer. - /// - protected Container Footer { get; private set; } - [Resolved] private ScreenFooter? footer { get; set; } - // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. - public virtual bool UseNewFooter => false; - /// /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. @@ -52,11 +43,6 @@ namespace osu.Game.Overlays.Mods /// protected Container MainAreaContent { get; private set; } = null!; - /// - /// A container for content that is to be displayed inside the footer. - /// - protected Container FooterContent { get; private set; } - protected override bool StartHidden => true; protected override bool BlockNonPositionalInput => true; @@ -75,8 +61,6 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load() { - const float footer_height = ScreenFooter.HEIGHT; - Child = TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, @@ -100,30 +84,9 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Top = ShearedOverlayHeader.HEIGHT, - Bottom = footer_height + PADDING, + Bottom = ScreenFooter.HEIGHT + PADDING, } }, - Footer = new InputBlockingContainer - { - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = footer_height, - Margin = new MarginPadding { Top = PADDING }, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5 - }, - FooterContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, - } - } } }; } @@ -160,7 +123,7 @@ namespace osu.Game.Overlays.Mods Header.MoveToY(0, fade_in_duration, Easing.OutQuint); - if (UseNewFooter && footer != null) + if (footer != null) { footer.SetOverlayContent(this); @@ -170,8 +133,6 @@ namespace osu.Game.Overlays.Mods hideFooterOnPopOut = true; } } - else - Footer.MoveToY(0, fade_in_duration, Easing.OutQuint); } protected override void PopOut() @@ -183,7 +144,7 @@ namespace osu.Game.Overlays.Mods Header.MoveToY(-Header.DrawHeight, fade_out_duration, Easing.OutQuint); - if (UseNewFooter && footer != null) + if (footer != null) { footer.ClearOverlayContent(); @@ -193,8 +154,6 @@ namespace osu.Game.Overlays.Mods hideFooterOnPopOut = false; } } - else - Footer.MoveToY(Footer.DrawHeight, fade_out_duration, Easing.OutQuint); } } } From 58c7d1e7724b6a08b01f4682ccc4684bc311dba0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 29 Jun 2024 10:19:22 +0300 Subject: [PATCH 044/670] Bind game-wide mods bindable to mod select overlay in new song select screen --- osu.Game/Screens/SelectV2/SongSelectV2.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/SelectV2/SongSelectV2.cs b/osu.Game/Screens/SelectV2/SongSelectV2.cs index a8730ad808..2f9667793f 100644 --- a/osu.Game/Screens/SelectV2/SongSelectV2.cs +++ b/osu.Game/Screens/SelectV2/SongSelectV2.cs @@ -64,18 +64,29 @@ namespace osu.Game.Screens.SelectV2 public override void OnEntering(ScreenTransitionEvent e) { this.FadeIn(); + + modSelectOverlay.SelectedMods.BindTo(Mods); + base.OnEntering(e); } public override void OnResuming(ScreenTransitionEvent e) { this.FadeIn(); + + // required due to https://github.com/ppy/osu-framework/issues/3218 + modSelectOverlay.SelectedMods.Disabled = false; + modSelectOverlay.SelectedMods.BindTo(Mods); + base.OnResuming(e); } public override void OnSuspending(ScreenTransitionEvent e) { this.Delay(400).FadeOut(); + + modSelectOverlay.SelectedMods.UnbindFrom(Mods); + base.OnSuspending(e); } From a65af8249c74e32e512e146943687af33555d155 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 07:27:19 +0300 Subject: [PATCH 045/670] Fix first-run setup buttons reset after reopening from dismiss --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 47f53a3fa6..6412297663 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,10 +150,16 @@ namespace osu.Game.Overlays private FirstRunSetupFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() => currentFooterContent = new FirstRunSetupFooterContent + public override Drawable CreateFooterContent() { - ShowNextStep = showNextStep, - }; + currentFooterContent = new FirstRunSetupFooterContent + { + ShowNextStep = showNextStep, + }; + + currentFooterContent.OnLoadComplete += _ => updateButtons(); + return currentFooterContent; + } public override bool OnBackButton() { From 901663b3fff921f78afe4e0bfa2fce97ce378b90 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 30 Jun 2024 11:00:00 +0300 Subject: [PATCH 046/670] Fix test failure --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index dedad3a40a..9f48b06bb6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -55,6 +55,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("clear contents", Clear); AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0)); AddStep("reset mods", () => SelectedMods.SetDefault()); + AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo)); AddStep("set up presets", () => { From 0c34e7bebbce5a989af00d8daed25331ff33321a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Jul 2024 06:48:05 +0300 Subject: [PATCH 047/670] Store layout version in `SkinLayoutVersion` instead and refactor migration code --- .../Archives/argon-layout-version-0.osk | Bin 0 -> 1550 bytes .../Archives/classic-layout-version-0.osk | Bin 0 -> 1382 bytes .../Archives/triangles-layout-version-0.osk | Bin 0 -> 1378 bytes .../Visual/Gameplay/TestSceneSkinEditor.cs | 119 ++++++++++-------- osu.Game/Database/RealmAccess.cs | 3 +- osu.Game/Skinning/ArgonSkin.cs | 23 ---- osu.Game/Skinning/LegacySkin.cs | 23 ---- osu.Game/Skinning/Skin.cs | 116 ++++++++++++----- osu.Game/Skinning/SkinImporter.cs | 2 - osu.Game/Skinning/SkinInfo.cs | 15 --- osu.Game/Skinning/SkinLayoutInfo.cs | 18 ++- 11 files changed, 165 insertions(+), 154 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk create mode 100644 osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk create mode 100644 osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk diff --git a/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk new file mode 100644 index 0000000000000000000000000000000000000000..f767033eb1de68d16bbb5c4fa9cb5a0fb145425d GIT binary patch literal 1550 zcmWIWW@Zs#U|`^2*qU%JLZU!8aS@Pr04&15P@J8aryU#0>+j!P%R^ZV zg}4>xPMtgTR>x7b?S}2;yNh-Te99H8o3uZn>FxoZ&$acRWS&pExZ=B&MdgApPaRI? z(gk*z3!Y?ss$+>fEq0aT?Jw3G`R8|9rytwm=6Y8p^n%5Cr^borgHH=K3P*ZvG2E`q z?f$8}cvjaZ`>3Fr6*?0|Z!d~a-1uywCWCZVC!GrLPwn(h=yT<8}Ug zcVN&310w^YOCc9dob^BHbK1w3(WuvSYKeo@obDiD?@!$hvz$I}`uyqiXN5=eDvzpm zR;teIRGs;=a$aTT&w%8wcN)W9u%@R!dz$)e>1)fT9EOi2A_nlPT3PLiRVP}MKN^@>S#MeMs(e1@6d@1uz@?tiQ^imGf3x}gznAY$ylqUq+BY%o zGGqJtB$aIWwb6?uO&=9G?5y19Gk@*WdAH6z3pSWGD}?i>le&YSGmAji7t6Vm@BK8i zJmk^EE#Y<{UBKz?F8QeKb&OhSM;9hr>prrUE9{8fzKioZ=b?oSF3L|!SI_6M;NE}m zk58YYOjpb2sK1HawJT?sta%$UG;N5 zrI_ECHt)Clp7;$5`+D!@rqBJ};JVSQjo;7BUMNxL;)}VwAK5o`bn1SLY5a92FJZ?# zqeU?X(-;<*3QxS%*Vrk!fG>on#8$Wb^W7OA6%QpnJ-xbF_qR6FqNmUA%t+qPyz$a% zrbi}IIAUjV@J8qf`Q`O}Sml}|=pZK;{cC?`!Cj{bJJL9mO|}c3(Bz#tZ}~#&I5iE? z)2|yYGk!2y`I7Oem0em^){~-*CzmdJ<&tDS$>ZChWiwoi7DycV{(ATA_Nf{i0y``x zOR{wRZA@R3cBaK(w{ooIOfN?W6`fz_!h6BZlzOlbS;rR9IPve7c-{;A2DMpviH^>U`HDJeaCupm-ZOzxDz zYJQ7J77LYH_NAzO+@o+rtaj;73)aN6$=}WixmCZOG1YP6%4p_D-{?Deg15id=0DGp zj%&XWcl-M%<1fLNrp%ta?2gmVOskzoPgm~#^JRvey_xL_Jvqn7N z^YVFa6U=o?Rx7axcO_r0*R=KT-!W5ia?tODYWD3K1=34PGS-$ComMRWspj3aqTk4= znCt51TNz^Rt9`Ai58t};zO`lY?9{nQ5xzUSCip#mzW#tF*E6y3z0s=cS4gd!|Ly0u zyy|-&{)UIv#@cWH`+feIqxJbVfA>Z8Mcw(h_v5<<@AOe~lg!klDm7p((gb2YAP&e$ ztW3*H%}GJz_99I$9e;k^z|eRCstaBSp=(7i5)fL`8F3X5=w_e?1j3B1Ea<@!;LXYg Pl4k+Jk3gE86~qGo@tS=8 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk b/osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk new file mode 100644 index 0000000000000000000000000000000000000000..8240510f7c20e7d192efbfbba03ff9243f6f51dc GIT binary patch literal 1382 zcmWIWW@Zs#U|`^2*p+ZEVoSr(w@ZM$LtqgGhT`nZJiW}kOx2!^T!$P4SbzWSS{}+` zD8;Hcck0}!w>pl_y8Yte-F0_jIqGJ&&VQmWu~B+PqTTxP$fWw@EwA=|^IMk@V&v`S z&v_-4cS@}DJ$uJ%n~px^^u5cmt$Y8S*7jpIM_Vr`gkG>Z@6;IhZ`m|uru0oyGSmE` z#rmF{Pcs+!T5pnPv-HU+7-5q<$ITi2E5u*dkGS$5jI*OaeF{OwU0OSR=ei)XDp zD;|L2lhb)8+8cnrm2uo0m(j@AWNL{+)tv4iVee1f4!4{>Z~FY{^k;=f z^D2+3c2=s+>{OlkvvOW#=FfoUuXh@!y+0xfLmn>7A_H4?JCCk*+!kb)5 zMH0HE?8st7b?Q;ev-K%V3=Et27#PHWPW4U9%<~9!an8>z$j?j7D=7xM`RX*^{96tJ zZSVhZb#IRR$=$47=&?{YG+MNEiLJ}Tt!B-8Q*ZW`UH1$)Qvd!|;Q_Yi7fx}taDTEs z{D$ZJkFE2q+AhxJ_|u@w6@PP5xxIvc)a5Lv%9fS;_Gzx`4bo2vpKX0%qTY1LUrpB= zo@pPFNaXy*$Ju9|ysz)ajC7wygWl;nmw&K237vE6ds=a#{MQM`X_-C7tBe?x*=7pO zm~~92TcdOT!Dn6T8+s$$GOyp6Z})hTMr_*k?3MOe2i^Q0+}QSf*3}bk?p0AP^B#X~ z-+f|R(3eTtF=AUwf`1>dIVKWknN?BZrDt~Szpt3qRnDIVqLo}Ko>w>49egyUd(VR4 zzB|!}Jz2qzr(9@l*z@RY?(+pN>mS(m?`)X- zVckY8$&9tCN5r`Pw0&y4ua$H?*C{60zyF6)pK;)wTj@?WKTmiY9k$=}%HKx4^vL7q zHuBFk|CV)s#}QGzjy;QaNG#s?` zy3eMhEA?#Pc;BfaxX|V4XRV2jx`E67D8_uQu8r$>@A!aa)}^G{RSgBbN4Z=yt}gr0 z{Wgm4sNjX#HBX;kc=*aMq4Vpfm%iHfL;W5F+wRWHuFjKZ{bek%NBMBk-(^?VuGEbV z`&j#X&eX|uoO#x__oJpunW;%tYQXfU3BO%H&sw+%ZJlFot z8i3*xCI8FTl|Wxi0b*Vt2Kgj2FD+j$t2jSz^|PRWfKT3Md_y+{1)S2?3k~TA)Y3fV zbH@9;r*2&2qLBigjx*epPKX3Z{>GWrX zNAoI=s&-bY&g@j3`Ll9fW#-R-#b56a(S zjV`hsCh1H-HwrT_hymT`o0ysB5$fWcpIeZhmzq~n40i0>aNqn}4gz~WYwMibU?kzO z@`z&VwvfeE%?XV*=R5d6P4`%%MOt9GS*pAH`VarVudp8IE_fA33* zyvlef`c>f%wX{vYF77g);mE9Kd+O}ptFP{z`Y<(jlFosMRY6CeX!kbs)UpV4^;r0x z_)z4)X0X`sdaY#TCjI1vjM>)~SD*dGx?+aN-eq@lCi-pmopoegWbI}pw-=lpra^I= z-8LSd`CoQY=bYvjoijdXeuQ02}qK9uhag|MQnq~ z!92b_Aq)0xu%5B*`WcPz3Hl6M(v$)+d4JCNrT93w#pJ=&(x;y1Zhx@-rg|vw$2RX3 z1#iL+lrY*Vt~y_9@AgXFJ#qKHQ^#)?Zd0?JZ*bP4Xt|FrPkrXgr8nR2v2JtN$;Zxr zTP~inPn)IHboR82t=vCm-OY=1pR?+Lw_r~I8;954%Q0S`izmM?@lxNvdGg8dg?mbw zmvoyL_8Q8hz2TjFv$9~y#r5W=b}jmMurkp7&^hU3`zic0|Mh5mWS7__`qO1&!=6&p zAMeE(clUf{+O=2aPv>$*7lkdSXEKDhpPA3CeDkH|mnCNLMUsnO2bSc|NRoT(qdv1k zL?&EF_@wma`QG^qnGC6U`)-OJ?A18O7`t@xRpmU98jG(D`8n0sR+(Sf@wNFy=@FV-6nCa^?SB@_hq%%&(_tRx2iwrzrXtR*FkO7 zKNokb(fQAWnlxpmCRM2clcXjP^8s-{PGV(RW@=6fBL5U=dg=K4obvp7#`|>mrl1vU zU5u`uyS*FdyogX(KW()bFtIZ-i7?A pp1%-U^B8gEHgq%40|H?NCkuM81bDNuf#g|$@FS3p2C86S004m&DMkPQ literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 2470c320cc..f44daa1ecb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -24,6 +25,7 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Skinning.Components; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -384,73 +386,82 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMigrationArgon() { - AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); - AddStep("add combo to global hud target", () => - { - globalHUDTarget.Add(new ArgonComboCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - }); + Live importedSkin = null!; - Live modifiedSkin = null!; + AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); - AddStep("select another skin", () => + AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter { - modifiedSkin = skins.CurrentSkinInfo.Value; - skins.CurrentSkinInfo.SetDefault(); - }); - AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); - AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); - AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is ArgonComboCounter)); - AddAssert("ruleset hud target contains both combos", () => - { - var target = rulesetHUDTarget; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); - return target.Components.Count == 2 && - target.Components[0] is ArgonComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && - target.Components[1] is ArgonComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; - }); - AddStep("save skin", () => skinEditor.Save()); - AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + } + + [Test] + public void TestMigrationTriangles() + { + Live importedSkin = null!; + + AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + + AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); + + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); } [Test] public void TestMigrationLegacy() { - AddStep("select legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + Live importedSkin = null!; - AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded); - AddStep("add combo to global hud target", () => + AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + + AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter { - globalHUDTarget.Add(new LegacyComboCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); - }); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(2f), + })); + AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value)); - Live modifiedSkin = null!; + AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault()); + AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin); + AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded); + AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType().Count() == 1); + AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); + } - AddStep("select another skin", () => - { - modifiedSkin = skins.CurrentSkinInfo.Value; - skins.CurrentSkinInfo.SetDefault(); - }); - AddStep("modify version", () => modifiedSkin.PerformWrite(s => s.LayoutVersion = 0)); - AddStep("select skin again", () => skins.CurrentSkinInfo.Value = modifiedSkin); - AddAssert("global hud target does not contain combo", () => !globalHUDTarget.Components.Any(c => c is LegacyComboCounter)); - AddAssert("ruleset hud target contains both combos", () => - { - var target = rulesetHUDTarget; - - return target.Components.Count == 2 && - target.Components[0] is LegacyComboCounter one && one.Anchor == Anchor.BottomLeft && one.Origin == Anchor.BottomLeft && - target.Components[1] is LegacyComboCounter two && two.Anchor == Anchor.Centre && two.Origin == Anchor.Centre; - }); - AddStep("save skin", () => skinEditor.Save()); - AddAssert("version updated", () => modifiedSkin.PerformRead(s => s.LayoutVersion) == SkinInfo.LATEST_LAYOUT_VERSION); + private Skin importSkinFromArchives(string filename) + { + var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely(); + return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); } protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 606bc5e10c..1ece81be50 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,9 +93,8 @@ namespace osu.Game.Database /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. - /// 42 2024-06-25 Add SkinInfo.LayoutVersion to allow performing migrations of components on structural changes. /// - private const int schema_version = 42; + private const int schema_version = 41; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 4cd54c06f0..707281db31 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -12,7 +12,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -70,28 +69,6 @@ namespace osu.Game.Skinning // Purple new Color4(92, 0, 241, 255), }; - - if (skin.LayoutVersion < 20240625 - && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) - && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) - { - var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(ArgonComboCounter)).ToArray(); - - if (comboCounters.Any()) - { - hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); - - resources.RealmAccess.Run(r => - { - foreach (var ruleset in r.All()) - { - hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) - ? rulesetComponents.Concat(comboCounters).ToArray() - : comboCounters); - } - }); - } - } } public override Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Textures?.Get(componentName, wrapModeS, wrapModeT); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f148bad96e..b71b626b4e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -19,7 +19,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Rulesets; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; @@ -57,28 +56,6 @@ namespace osu.Game.Skinning protected LegacySkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore, string configurationFilename = @"skin.ini") : base(skin, resources, fallbackStore, configurationFilename) { - if (resources != null - && skin.LayoutVersion < 20240625 - && LayoutInfos.TryGetValue(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, out var hudLayout) - && hudLayout.TryGetDrawableInfo(null, out var hudComponents)) - { - var comboCounters = hudComponents.Where(h => h.Type.Name == nameof(LegacyComboCounter)).ToArray(); - - if (comboCounters.Any()) - { - hudLayout.Update(null, hudComponents.Except(comboCounters).ToArray()); - - resources.RealmAccess.Run(r => - { - foreach (var ruleset in r.All()) - { - hudLayout.Update(ruleset, hudLayout.TryGetDrawableInfo(ruleset, out var rulesetComponents) - ? rulesetComponents.Concat(comboCounters).ToArray() - : comboCounters); - } - }); - } - } } protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e4ca908d90..5bac5c3d81 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -21,11 +21,15 @@ using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { public abstract class Skin : IDisposable, ISkin { + private readonly IStorageResourceProvider? resources; + /// /// A texture store which can be used to perform user file lookups for this skin. /// @@ -68,6 +72,8 @@ namespace osu.Game.Skinning /// An optional filename to read the skin configuration from. If not provided, the configuration will be retrieved from the storage using "skin.ini". protected Skin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? fallbackStore = null, string configurationFilename = @"skin.ini") { + this.resources = resources; + Name = skin.Name; if (resources != null) @@ -131,40 +137,9 @@ namespace osu.Game.Skinning { string jsonContent = Encoding.UTF8.GetString(bytes); - SkinLayoutInfo? layoutInfo = null; - - // handle namespace changes... - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); - jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); - - try - { - // First attempt to deserialise using the new SkinLayoutInfo format - layoutInfo = JsonConvert.DeserializeObject(jsonContent); - } - catch - { - } - - // Of note, the migration code below runs on read of skins, but there's nothing to - // force a rewrite after migration. Let's not remove these migration rules until we - // have something in place to ensure we don't end up breaking skins of users that haven't - // manually saved their skin since a change was implemented. - - // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. + var layoutInfo = parseLayoutInfo(jsonContent, skinnableTarget); if (layoutInfo == null) - { - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); - - if (deserializedContent == null) - continue; - - layoutInfo = new SkinLayoutInfo(); - layoutInfo.Update(null, deserializedContent.ToArray()); - - Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format"); - } + continue; LayoutInfos[skinnableTarget] = layoutInfo; } @@ -230,6 +205,81 @@ namespace osu.Game.Skinning return null; } + #region Deserialisation & Migration + + private SkinLayoutInfo? parseLayoutInfo(string jsonContent, SkinComponentsContainerLookup.TargetArea target) + { + SkinLayoutInfo? layout = null; + + // handle namespace changes... + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter"); + + try + { + // First attempt to deserialise using the new SkinLayoutInfo format + layout = JsonConvert.DeserializeObject(jsonContent); + } + catch + { + } + + // If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list. + if (layout == null) + { + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + if (deserializedContent == null) + return null; + + layout = new SkinLayoutInfo { Version = 0 }; + layout.Update(null, deserializedContent.ToArray()); + + Logger.Log($"Ferrying {deserializedContent.Count()} components in {target} to global section of new {nameof(SkinLayoutInfo)} format"); + } + + for (int i = layout.Version + 1; i <= SkinLayoutInfo.LATEST_VERSION; i++) + applyMigration(layout, target, i); + + layout.Version = SkinLayoutInfo.LATEST_VERSION; + return layout; + } + + private void applyMigration(SkinLayoutInfo layout, SkinComponentsContainerLookup.TargetArea target, int version) + { + switch (version) + { + case 1: + { + if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents || + !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || + resources == null) + break; + + var comboCounters = globalHUDComponents.Where(c => + c.Type.Name == nameof(LegacyComboCounter) || + c.Type.Name == nameof(DefaultComboCounter) || + c.Type.Name == nameof(ArgonComboCounter)).ToArray(); + + layout.Update(null, globalHUDComponents.Except(comboCounters).ToArray()); + + resources.RealmAccess.Run(r => + { + foreach (var ruleset in r.All()) + { + layout.Update(ruleset, layout.TryGetDrawableInfo(ruleset, out var rulesetHUDComponents) + ? rulesetHUDComponents.Concat(comboCounters).ToArray() + : comboCounters); + } + }); + + break; + } + } + } + + #endregion + #region Disposal ~Skin() diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 714427f40d..59c7f0ba26 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -223,8 +223,6 @@ namespace osu.Game.Skinning } } - s.LayoutVersion = SkinInfo.LATEST_LAYOUT_VERSION; - string newHash = ComputeHash(s); hadChanges = newHash != s.Hash; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index a3d5771b5e..9763d3b57e 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -39,21 +39,6 @@ namespace osu.Game.Skinning public bool Protected { get; set; } - /// - /// The latest version in YYYYMMDD format for skin layout migrations. - /// - /// - /// - /// 20240625: Moves combo counters from ruleset-agnostic to ruleset-specific HUD targets. - /// - /// - public const int LATEST_LAYOUT_VERSION = 20240625; - - /// - /// A version in YYYYMMDD format for applying skin layout migrations. - /// - public int LayoutVersion { get; set; } - public virtual Skin CreateInstance(IStorageResourceProvider resources) { var type = string.IsNullOrEmpty(InstantiationInfo) diff --git a/osu.Game/Skinning/SkinLayoutInfo.cs b/osu.Game/Skinning/SkinLayoutInfo.cs index 115d59b9d0..22c876e5ad 100644 --- a/osu.Game/Skinning/SkinLayoutInfo.cs +++ b/osu.Game/Skinning/SkinLayoutInfo.cs @@ -19,12 +19,26 @@ namespace osu.Game.Skinning { private const string global_identifier = @"global"; - [JsonIgnore] - public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); + /// + /// Latest version representing the schema of the skin layout. + /// + /// + /// + /// 0: Initial version of all skin layouts. + /// 1: Moves existing combo counters from global to per-ruleset HUD targets. + /// + /// + public const int LATEST_VERSION = 1; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public int Version = LATEST_VERSION; [JsonProperty] public Dictionary DrawableInfo { get; set; } = new Dictionary(); + [JsonIgnore] + public IEnumerable AllDrawables => DrawableInfo.Values.SelectMany(v => v); + public bool TryGetDrawableInfo(RulesetInfo? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) => DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components); From 1d94c96a8efef24d5ae54aa7159f6e7a615a50a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 2 Jul 2024 14:40:00 +0300 Subject: [PATCH 048/670] Display customisation header in disabled state when no customisable mod selected --- .../UserInterface/TestSceneModSelectOverlay.cs | 2 +- osu.Game/Localisation/ModSelectOverlayStrings.cs | 5 +++++ osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 15 +++++++++++++-- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 8 ++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 5 +++-- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9f48b06bb6..21a5e3082b 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -1003,7 +1003,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { - AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().State.Value == (disabled ? Visibility.Hidden : Visibility.Visible)); + AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded.Value == active); } diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index a2e1df42c6..10037d30c3 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -80,6 +80,11 @@ namespace osu.Game.Localisation /// public static LocalisableString CustomisationPanelHeader => new TranslatableString(getKey(@"customisation_panel_header"), @"Customise"); + /// + /// "No mod selected which can be customised." + /// + public static LocalisableString CustomisationPanelDisabledReason => new TranslatableString(getKey(@"customisation_panel_disabled_reason"), @"No mod selected which can be customised."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index e6534921f6..2887b53548 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -30,6 +30,12 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Expanded = new BindableBool(); + public ModCustomisationHeader() + { + Action = Expanded.Toggle; + Enabled.Value = false; + } + [BackgroundDependencyLoader] private void load() { @@ -75,12 +81,17 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + Enabled.BindValueChanged(e => + { + TooltipText = e.NewValue + ? string.Empty + : ModSelectOverlayStrings.CustomisationPanelDisabledReason; + }, true); + Expanded.BindValueChanged(v => { icon.RotateTo(v.NewValue ? 180 : 0); }, true); - - Action = Expanded.Toggle; } } } diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a5a524e109..240569031c 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -33,6 +33,8 @@ namespace osu.Game.Overlays.Mods [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public readonly BindableBool Enabled = new BindableBool(); + public readonly BindableBool Expanded = new BindableBool(); public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); @@ -55,6 +57,7 @@ namespace osu.Game.Overlays.Mods Depth = float.MinValue, RelativeSizeAxes = Axes.X, Height = header_height, + Enabled = { BindTarget = Enabled }, Expanded = { BindTarget = Expanded }, }, content = new FocusGrabbingContainer @@ -107,6 +110,11 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); + Enabled.BindValueChanged(e => + { + this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint); + }, true); + Expanded.BindValueChanged(_ => updateDisplay(), true); SelectedMods.BindValueChanged(_ => updateMods(), true); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index b0d58480db..d5a4d27237 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -219,6 +219,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 400, + State = { Value = Visibility.Visible }, } } } @@ -493,7 +494,7 @@ namespace osu.Game.Overlays.Mods if (anyCustomisableModActive) { - customisationPanel.Show(); + customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) customisationPanel.Expanded.Value = true; @@ -501,7 +502,7 @@ namespace osu.Game.Overlays.Mods else { customisationPanel.Expanded.Value = false; - customisationPanel.Hide(); + customisationPanel.Enabled.Value = false; } } From 153138cdac5707988a1b351e96b4019f5512b13f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 3 Jul 2024 13:47:41 +0900 Subject: [PATCH 049/670] Use `null` to disable audio filter instead --- osu.Game/Overlays/MusicController.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 678ae92d4b..7fe9a3e33b 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; @@ -259,13 +260,15 @@ namespace osu.Game.Overlays /// /// Duration of the ducking transition, in ms. /// Level to drop volume to (1.0 = 100%). - /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Easing for the ducking transition. - public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, Easing easing = Easing.InCubic) + public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.InCubic) { Schedule(() => { - audioDuckFilter?.CutoffTo(duckCutoffTo, duration, easing); + if (duckCutoffTo.IsNotNull()) + audioDuckFilter?.CutoffTo((int)duckCutoffTo, duration, easing); + this.TransformBindableTo(audioDuckVolume, duckVolumeTo, duration, easing); }); } @@ -291,10 +294,10 @@ namespace osu.Game.Overlays /// Duration of the unducking transition, in ms. /// Easing for the unducking transition. /// Level to drop volume to (1.0 = 100%). - /// Cutoff frequency to drop `AudioFilter` to. Use `AudioFilter.MAX_LOWPASS_CUTOFF` to skip filter effect. + /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Duration of the ducking transition, in ms. /// Easing for the ducking transition. - public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.InCubic) + public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.InCubic) { Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); Scheduler.AddDelayed(() => Unduck(unduckDuration, unduckEasing), delay); From b972632e4fdcfd2a6bfb866b1ab06ade5ee16c27 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 3 Jul 2024 13:50:35 +0900 Subject: [PATCH 050/670] Change default easing to match prior behaviour --- osu.Game/Overlays/MusicController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 7fe9a3e33b..b0e6994448 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -262,7 +262,7 @@ namespace osu.Game.Overlays /// Level to drop volume to (1.0 = 100%). /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Easing for the ducking transition. - public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.InCubic) + public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic) { Schedule(() => { @@ -297,7 +297,7 @@ namespace osu.Game.Overlays /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Duration of the ducking transition, in ms. /// Easing for the ducking transition. - public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.InCubic) + public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.OutCubic) { Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); Scheduler.AddDelayed(() => Unduck(unduckDuration, unduckEasing), delay); From d29d114133e0ecd5426a15497234718edb6e0546 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 3 Jul 2024 13:52:20 +0900 Subject: [PATCH 051/670] Match prior ducking behaviour --- osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index e01d4e2d66..cdde35c7d0 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.Dialog protected override void Confirm() { - musicController?.Duck(); + musicController?.Duck(100, 1f); confirmSample?.Play(); base.Confirm(); } From 6fda0db9bac3c30b7d5c2fcaf15a1a4bafac09aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jul 2024 11:03:52 +0200 Subject: [PATCH 052/670] Fix test --- .../TestSceneModCustomisationPanel.cs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 360c28acfa..9c0d185892 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + private ModCustomisationPanel panel = null!; + [SetUp] public void SetUp() => Schedule(() => { @@ -25,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20f), - Child = new ModCustomisationPanel + Child = panel = new ModCustomisationPanel { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -39,10 +41,26 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDisplay() { - AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); - AddStep("set DA", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }); - AddStep("set FL+WU+DA+AD", () => SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }); - AddStep("set empty", () => SelectedMods.Value = Array.Empty()); + AddStep("set DT", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = panel.Expanded.Value = true; + }); + AddStep("set DA", () => + { + SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; + panel.Enabled.Value = panel.Expanded.Value = true; + }); + AddStep("set FL+WU+DA+AD", () => + { + SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; + panel.Enabled.Value = panel.Expanded.Value = true; + }); + AddStep("set empty", () => + { + SelectedMods.Value = Array.Empty(); + panel.Enabled.Value = panel.Expanded.Value = false; + }); } } } From 8dd04b6e9a699c51fef83384e84bca800a451661 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 4 Jul 2024 00:39:12 +0200 Subject: [PATCH 053/670] update nodesamples on placement --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 5cb9adfd72..a30434638f 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -163,6 +163,15 @@ namespace osu.Game.Rulesets.Edit if (lastHitNormal != null) HitObject.Samples[0] = lastHitNormal; } + + if (HitObject is IHasRepeats hasRepeats) + { + // Make sure all the node samples are identical to the hit object's samples + for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) + { + hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); + } + } } /// From 8f3a30b0b9ba86adfd6b8c0690fc716608abad2c Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 4 Jul 2024 00:56:53 +0200 Subject: [PATCH 054/670] inherit addition bank from last hitobject --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 5cb9adfd72..762a714088 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -158,10 +158,16 @@ namespace osu.Game.Rulesets.Edit if (AutomaticBankAssignment) { - // Take the hitnormal sample of the last hit object - var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (lastHitNormal != null) - HitObject.Samples[0] = lastHitNormal; + // Create samples based on the sample settings of the previous hit object + var lastHitObject = getPreviousHitObject(); + + if (lastHitObject != null) + { + for (int i = 0; i < HitObject.Samples.Count; i++) + { + HitObject.Samples[i] = lastHitObject.CreateHitSampleInfo(HitObject.Samples[i].Name); + } + } } } From e754668daa3dc8d57d7d4f62aa61735df3e4798e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 4 Jul 2024 01:09:06 +0200 Subject: [PATCH 055/670] Always inherit the volume from the previous hit object --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 5cb9adfd72..84fe1584dd 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -163,6 +163,19 @@ namespace osu.Game.Rulesets.Edit if (lastHitNormal != null) HitObject.Samples[0] = lastHitNormal; } + else + { + // Only inherit the volume from the previous hit object + var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + + if (lastHitNormal != null) + { + for (int i = 0; i < HitObject.Samples.Count; i++) + { + HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); + } + } + } } /// From 86583898548c83e3211b6c10f653b88382ada954 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jul 2024 07:22:08 +0300 Subject: [PATCH 056/670] Clarify how the panel blocks input --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 240569031c..ae162e1553 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -39,12 +39,14 @@ namespace osu.Game.Overlays.Mods public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + // Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded. + // These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside + // (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work). public override bool HandlePositionalInput => Expanded.Value; - public override bool HandleNonPositionalInput => Expanded.Value; - protected override bool BlockPositionalInput => true; - [BackgroundDependencyLoader] private void load() { @@ -125,8 +127,6 @@ namespace osu.Game.Overlays.Mods protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - protected override bool OnClick(ClickEvent e) { Expanded.Value = false; From 33711ba6167f3e0bdb7e9c7aba2b6f1dbde239ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jul 2024 07:25:54 +0300 Subject: [PATCH 057/670] Remove scroll-dropdown-into-view logic --- .../Configuration/SettingSourceAttribute.cs | 35 ++----------------- 1 file changed, 2 insertions(+), 33 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index f87c8de0cb..1e425c88a6 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -13,10 +13,8 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Localisation; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; -using osuTK; namespace osu.Game.Configuration { @@ -276,37 +274,8 @@ namespace osu.Game.Configuration private partial class ModDropdownControl : DropdownControl { - protected override DropdownMenu CreateMenu() => new ModDropdownMenu(); - - private partial class ModDropdownMenu : OsuDropdownMenu - { - public ModDropdownMenu() - { - // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). - MaxHeight = 100; - } - - protected override void UpdateSize(Vector2 newSize) - { - base.UpdateSize(newSize); - - // todo: probably move this to OsuDropdown so that settings overlay can benefit from this as well. - if (newSize.Y > 0) - { - var scroll = this.FindClosestParent(); - - if (scroll != null) - { - const float padding = 15; - - float target = scroll.GetChildPosInContent(this, new Vector2(0, newSize.Y + padding)); - - if (target > scroll.Current + scroll.DisplayableContent) - scroll.ScrollTo(target - scroll.DisplayableContent); - } - } - } - } + // Set menu's max height low enough to workaround nested scroll issues (see https://github.com/ppy/osu-framework/issues/4536). + protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 100); } } } From 585e09981fd655add5cd0465d5cf6debb3eef97d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jul 2024 07:36:31 +0300 Subject: [PATCH 058/670] Allow scrollbar to overlap content The content is already padded enough to have the scrollbar sit on top. Having the content change padding when the scrollbar appears gives an unpleasent experience (especially when the scrollbar is hidden at first but the user increases the content's height by clicking on a dropdown or something) --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index ae162e1553..f214bcb3a1 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -87,7 +87,6 @@ namespace osu.Game.Overlays.Mods scrollContainer = new OsuScrollContainer(Direction.Vertical) { RelativeSizeAxes = Axes.X, - ScrollbarOverlapsContent = false, // The +2f is a workaround for masking issues (see https://github.com/ppy/osu-framework/issues/1675#issuecomment-910023157) // Note that this actually causes the full scroll range to be reduced by 2px at the bottom, but it's not really noticeable. Margin = new MarginPadding { Top = header_height + 2f }, From d948193757530830d5488b531508fb96e0816362 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 4 Jul 2024 14:23:35 +0900 Subject: [PATCH 059/670] Change Duck() to be IDisposable and prevent overlapping usages --- .../Collections/ManageCollectionsDialog.cs | 16 ++++++-- .../Dialog/PopupDialogDangerousButton.cs | 10 ----- osu.Game/Overlays/DialogOverlay.cs | 13 +++++- osu.Game/Overlays/MusicController.cs | 41 ++++++++++++------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index e777da05e5..0396fd531c 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -1,8 +1,8 @@ // 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.Framework.Graphics.Shapes; @@ -24,6 +24,8 @@ namespace osu.Game.Collections protected override string PopInSampleName => @"UI/overlay-big-pop-in"; protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; + private IDisposable? audioDucker; + [Resolved] private MusicController? musicController { get; set; } @@ -40,7 +42,7 @@ namespace osu.Game.Collections } [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) + private void load(OsuColour colours) { Children = new Drawable[] { @@ -115,9 +117,15 @@ namespace osu.Game.Collections }; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + audioDucker?.Dispose(); + } + protected override void PopIn() { - musicController?.Duck(100, 1f); + audioDucker = musicController?.Duck(100, 1f, unduckDuration: 100); this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); @@ -127,7 +135,7 @@ namespace osu.Game.Collections { base.PopOut(); - musicController?.Unduck(100); + audioDucker?.Dispose(); this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index cdde35c7d0..1878cfd131 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -47,9 +47,6 @@ namespace osu.Game.Overlays.Dialog private partial class DangerousConfirmContainer : HoldToConfirmContainer { - [Resolved] - private MusicController musicController { get; set; } - public DangerousConfirmContainer() : base(isDangerousAction: true) { @@ -73,15 +70,8 @@ namespace osu.Game.Overlays.Dialog Progress.BindValueChanged(progressChanged); } - protected override void AbortConfirm() - { - musicController?.Unduck(); - base.AbortConfirm(); - } - protected override void Confirm() { - musicController?.Duck(100, 1f); confirmSample?.Play(); base.Confirm(); } diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index aa9bb99e01..57cfa49746 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Dialog; @@ -29,6 +30,8 @@ namespace osu.Game.Overlays public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; + private IDisposable? audioDucker; + public DialogOverlay() { AutoSizeAxes = Axes.Y; @@ -45,6 +48,12 @@ namespace osu.Game.Overlays Origin = Anchor.Centre; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + audioDucker?.Dispose(); + } + public void Push(PopupDialog dialog) { if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return; @@ -95,13 +104,13 @@ namespace osu.Game.Overlays protected override void PopIn() { - musicController.Duck(100, 1f); + audioDucker = musicController.Duck(100, 1f, unduckDuration: 100); } protected override void PopOut() { base.PopOut(); - musicController.Unduck(100); + audioDucker?.Dispose(); // PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present. if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b0e6994448..2952d9d18e 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -67,6 +67,7 @@ namespace osu.Game.Overlays private AudioFilter audioDuckFilter; private readonly BindableDouble audioDuckVolume = new BindableDouble(1); + private bool audioDuckActive; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -262,8 +263,15 @@ namespace osu.Game.Overlays /// Level to drop volume to (1.0 = 100%). /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Easing for the ducking transition. - public void Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic) + /// Duration of the unducking transition, in ms. + /// Easing for the unducking transition. + public IDisposable Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic) { + if (audioDuckActive) + throw new InvalidOperationException("Cannot perform Duck() while another Duck() is in progress."); + + audioDuckActive = true; + Schedule(() => { if (duckCutoffTo.IsNotNull()) @@ -271,20 +279,8 @@ namespace osu.Game.Overlays this.TransformBindableTo(audioDuckVolume, duckVolumeTo, duration, easing); }); - } - /// - /// Restores the volume to full and stops filtering the currently playing track after having used . - /// - /// Duration of the unducking transition, in ms. - /// Easing for the unducking transition. - public void Unduck(int duration = 500, Easing easing = Easing.InCubic) - { - Schedule(() => - { - audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, duration, easing); - this.TransformBindableTo(audioDuckVolume, 1, duration, easing); - }); + return new InvokeOnDisposal(() => unduck(unduckDuration, unduckEasing)); } /// @@ -299,8 +295,23 @@ namespace osu.Game.Overlays /// Easing for the ducking transition. public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.OutCubic) { + if (audioDuckActive) return; + Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); - Scheduler.AddDelayed(() => Unduck(unduckDuration, unduckEasing), delay); + Scheduler.AddDelayed(() => unduck(unduckDuration, unduckEasing), delay); + } + + private void unduck(int duration, Easing easing) + { + if (!audioDuckActive) return; + + audioDuckActive = false; + + Schedule(() => + { + audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, duration, easing); + this.TransformBindableTo(audioDuckVolume, 1, duration, easing); + }); } private bool next() From 753463fadbb8f50b78c7ad71abcb6a795d6c0aa5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 4 Jul 2024 15:01:37 +0900 Subject: [PATCH 060/670] Fix code style --- osu.Game/Overlays/DialogOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 57cfa49746..7c52081053 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -10,6 +10,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Input.Events; @@ -30,7 +31,8 @@ namespace osu.Game.Overlays public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; - private IDisposable? audioDucker; + [CanBeNull] + private IDisposable audioDucker; public DialogOverlay() { From 82e4e884d7a3c8ac81b52c0f16b93c5f06a81810 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 4 Jul 2024 15:51:50 +0900 Subject: [PATCH 061/670] Change overlapping `Duck()` usages to be a noop instead of a throw --- osu.Game/Overlays/MusicController.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 2952d9d18e..8b52c59bae 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -267,8 +267,7 @@ namespace osu.Game.Overlays /// Easing for the unducking transition. public IDisposable Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic) { - if (audioDuckActive) - throw new InvalidOperationException("Cannot perform Duck() while another Duck() is in progress."); + if (audioDuckActive) return null; audioDuckActive = true; From a5077fcb3f3e43ce237e3aee5486acaddd20b5aa Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 4 Jul 2024 17:22:33 +0900 Subject: [PATCH 062/670] Rename TimedDuck -> DuckMomentarily --- osu.Game/Overlays/MusicController.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8b52c59bae..4c563d8845 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -292,7 +292,7 @@ namespace osu.Game.Overlays /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Duration of the ducking transition, in ms. /// Easing for the ducking transition. - public void TimedDuck(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.OutCubic) + public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.OutCubic) { if (audioDuckActive) return; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 7da6b76aaa..c39ff60b6b 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Toolbar rulesetSelectionChannel[r.NewValue] = channel; channel.Play(); - musicController?.TimedDuck(600); + musicController?.DuckMomentarily(600); } public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 7a0a5620e10ec5f046029311bdf7c561b0529204 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jul 2024 14:06:18 +0300 Subject: [PATCH 063/670] Add failing test case --- .../Editing/TestSceneDifficultySwitching.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index 76ed5063b0..457d4cee34 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -12,7 +12,9 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Storyboards; @@ -169,6 +171,24 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("stack empty", () => Stack.CurrentScreen == null); } + [Test] + public void TestSwitchToDifficultyOfAnotherRuleset() + { + BeatmapInfo targetDifficulty = null; + + AddAssert("ruleset is catch", () => Ruleset.Value.CreateInstance() is CatchRuleset); + + AddStep("set taiko difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)); + switchToDifficulty(() => targetDifficulty); + confirmEditingBeatmap(() => targetDifficulty); + + AddAssert("ruleset switched to taiko", () => Ruleset.Value.CreateInstance() is TaikoRuleset); + + AddStep("exit editor forcefully", () => Stack.Exit()); + // ensure editor loader didn't resume. + AddAssert("stack empty", () => Stack.CurrentScreen == null); + } + private void switchToDifficulty(Func difficulty) => AddStep("switch to difficulty", () => Editor.SwitchToDifficulty(difficulty.Invoke())); private void confirmEditingBeatmap(Func targetDifficulty) From 207ee8a2eea0d981efc07715559d9f8c212240b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jul 2024 14:06:36 +0300 Subject: [PATCH 064/670] Fix editor not updating ruleset when switching difficulty --- osu.Game/Screens/Edit/EditorLoader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 8bcfa7b9f0..0e0fb9f795 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -121,7 +121,11 @@ namespace osu.Game.Screens.Edit scheduledDifficultySwitch = Schedule(() => { - Beatmap.Value = nextBeatmap.Invoke(); + var workingBeatmap = nextBeatmap.Invoke(); + + Ruleset.Value = workingBeatmap.BeatmapInfo.Ruleset; + Beatmap.Value = workingBeatmap; + state = editorState; // This screen is a weird exception to the rule that nothing after song select changes the global beatmap. From b2af49c1021d904a61e679835bd3cf2073bee170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2024 18:04:38 +0900 Subject: [PATCH 065/670] Fix classic fallback not having a transformer (and only add if required) --- .../Skinning/BeatmapSkinProvidingContainer.cs | 34 +++++++++++-------- .../Skinning/RulesetSkinProvidingContainer.cs | 20 +++++++---- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 4486c8a9f0..94c7a3aac6 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,9 +15,9 @@ namespace osu.Game.Skinning /// public partial class BeatmapSkinProvidingContainer : SkinProvidingContainer { - private Bindable beatmapSkins; - private Bindable beatmapColours; - private Bindable beatmapHitsounds; + private Bindable beatmapSkins = null!; + private Bindable beatmapColours = null!; + private Bindable beatmapHitsounds = null!; protected override bool AllowConfigurationLookup { @@ -68,11 +66,15 @@ namespace osu.Game.Skinning } private readonly ISkin skin; + private readonly ISkin? classicFallback; - public BeatmapSkinProvidingContainer(ISkin skin) + private Bindable currentSkin = null!; + + public BeatmapSkinProvidingContainer(ISkin skin, ISkin? classicFallback = null) : base(skin) { this.skin = skin; + this.classicFallback = classicFallback; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -93,15 +95,19 @@ namespace osu.Game.Skinning beatmapColours.BindValueChanged(_ => TriggerSourceChanged()); beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); - // If the beatmap skin looks to have skinnable resources, add the default classic skin as a fallback opportunity. - if (skin is LegacySkinTransformer legacySkin && legacySkin.IsProvidingLegacyResources) + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(_ => { - SetSources(new[] - { - skin, - skins.DefaultClassicSkin - }); - } + bool userSkinIsLegacy = skins.CurrentSkin.Value is LegacySkin; + bool beatmapProvidingResources = skin is LegacySkinTransformer legacySkin && legacySkin.IsProvidingLegacyResources; + + // If the beatmap skin looks to have skinnable resources and the user's skin choice is not a legacy skin, + // add the default classic skin as a fallback opportunity. + if (!userSkinIsLegacy && beatmapProvidingResources && classicFallback != null) + SetSources(new[] { skin, classicFallback }); + else + SetSources(new[] { skin }); + }, true); } } } diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs index 07e238243b..d736f4cdb5 100644 --- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs +++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs @@ -28,25 +28,33 @@ namespace osu.Game.Skinning protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; + [CanBeNull] + private readonly ISkin beatmapSkin; + /// /// This container already re-exposes all parent sources in a ruleset-usable form. /// Therefore disallow falling back to any parent any further. /// protected override bool AllowFallingBackToParent => false; - protected override Container Content { get; } + protected override Container Content { get; } = new Container + { + RelativeSizeAxes = Axes.Both, + }; public RulesetSkinProvidingContainer(Ruleset ruleset, IBeatmap beatmap, [CanBeNull] ISkin beatmapSkin) { Ruleset = ruleset; Beatmap = beatmap; + this.beatmapSkin = beatmapSkin; + } - InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin)) + [BackgroundDependencyLoader] + private void load(SkinManager skinManager) + { + InternalChild = new BeatmapSkinProvidingContainer(GetRulesetTransformedSkin(beatmapSkin), GetRulesetTransformedSkin(skinManager.DefaultClassicSkin)) { - Child = Content = new Container - { - RelativeSizeAxes = Axes.Both, - } + Child = Content, }; } From e3c8bee7d0ec7afd1a4087fd5c68cb67aac28049 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jul 2024 19:45:41 +0900 Subject: [PATCH 066/670] Fix nullability failure --- osu.Game/Skinning/SkinProvidingContainer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index acb15da80e..9aff187c9c 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -201,7 +202,10 @@ namespace osu.Game.Skinning source.SourceChanged -= TriggerSourceChanged; } - skinSources = sources.Select(skin => (skin, new DisableableSkinSource(skin, this))).ToArray(); + skinSources = sources + // Shouldn't be required after NRT is applied to all calling sources. + .Where(skin => skin.IsNotNull()) + .Select(skin => (skin, new DisableableSkinSource(skin, this))).ToArray(); foreach (var skin in skinSources) { From b29e535ca5372abc5461c434839c37c869f7ea8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jun 2024 10:42:09 +0200 Subject: [PATCH 067/670] Add results screen for displaying arbitrary daily challenge scores At this point its primary usage is the daily challenge event feed, but the leaderboard will be using this too shortly. Because the playlists results screen that exists in `master` is hard-coupled to showing the *local user's* best result on a given playlist by way of hard-coupling itself to the relevant API request, allowing show of *arbitrary* score by ID requires a whole bunch of subclassery as things stand. Oh well. Class naming is... best effort, due to the above. --- .../TestScenePlaylistsResultsScreen.cs | 2 +- .../Online/Rooms/ShowPlaylistScoreRequest.cs | 23 +++++++ .../DailyChallenge/DailyChallenge.cs | 5 ++ .../DailyChallenge/DailyChallengeEventFeed.cs | 8 ++- .../Multiplayer/MultiplayerResultsScreen.cs | 2 +- ...Screen.cs => PlaylistItemResultsScreen.cs} | 64 +++++++++---------- .../PlaylistItemScoreResultsScreen.cs | 37 +++++++++++ .../PlaylistItemUserResultsScreen.cs | 46 +++++++++++++ .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 2 +- 10 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs rename osu.Game/Screens/OnlinePlay/Playlists/{PlaylistsResultsScreen.cs => PlaylistItemResultsScreen.cs} (80%) create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index fca965052f..a52d29a120 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -413,7 +413,7 @@ namespace osu.Game.Tests.Visual.Playlists }; } - private partial class TestResultsScreen : PlaylistsResultsScreen + private partial class TestResultsScreen : PlaylistItemUserResultsScreen { public new LoadingSpinner LeftSpinner => base.LeftSpinner; public new LoadingSpinner CentreSpinner => base.CentreSpinner; diff --git a/osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs new file mode 100644 index 0000000000..d8f977a1d4 --- /dev/null +++ b/osu.Game/Online/Rooms/ShowPlaylistScoreRequest.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API; + +namespace osu.Game.Online.Rooms +{ + public class ShowPlaylistScoreRequest : APIRequest + { + private readonly long roomId; + private readonly long playlistItemId; + private readonly long scoreId; + + public ShowPlaylistScoreRequest(long roomId, long playlistItemId, long scoreId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.scoreId = scoreId; + } + + protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index b8d0dbbe7d..381c713233 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -209,6 +209,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge feed = new DailyChallengeEventFeed { RelativeSizeAxes = Axes.Both, + PresentScore = id => + { + if (this.IsCurrentScreen()) + this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id)); + } } ], }, diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index c38a921e43..e76238abad 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -19,6 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { private DailyChallengeEventFeedFlow flow = null!; + public Action? PresentScore { get; init; } + [BackgroundDependencyLoader] private void load() { @@ -48,6 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + PresentScore = PresentScore, }; flow.Add(row); row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire(); @@ -78,6 +82,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { private readonly NewScoreEvent newScore; + public Action? PresentScore { get; init; } + public NewScoreEventRow(NewScoreEvent newScore) { this.newScore = newScore; @@ -115,7 +121,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge text.AddUserLink(newScore.User); text.AddText(" got "); - text.AddLink($"{newScore.TotalScore:N0} points", () => { }); // TODO: present the score here + text.AddLink($"{newScore.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.ScoreID)); if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs index 6ed75508dc..c439df82a6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public partial class MultiplayerResultsScreen : PlaylistsResultsScreen + public partial class MultiplayerResultsScreen : PlaylistItemUserResultsScreen { public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs similarity index 80% rename from osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs index fdb83b5ae8..51fd912ccc 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs @@ -17,10 +17,10 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Playlists { - public partial class PlaylistsResultsScreen : ResultsScreen + public abstract partial class PlaylistItemResultsScreen : ResultsScreen { - private readonly long roomId; - private readonly PlaylistItem playlistItem; + protected readonly long RoomId; + protected readonly PlaylistItem PlaylistItem; protected LoadingSpinner LeftSpinner { get; private set; } = null!; protected LoadingSpinner CentreSpinner { get; private set; } = null!; @@ -30,19 +30,19 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private MultiplayerScores? lowerScores; [Resolved] - private IAPIProvider api { get; set; } = null!; + protected IAPIProvider API { get; private set; } = null!; [Resolved] - private ScoreManager scoreManager { get; set; } = null!; + protected ScoreManager ScoreManager { get; private set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } = null!; + protected RulesetStore Rulesets { get; private set; } = null!; - public PlaylistsResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) + protected PlaylistItemResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) : base(score) { - this.roomId = roomId; - this.playlistItem = playlistItem; + RoomId = roomId; + PlaylistItem = playlistItem; } [BackgroundDependencyLoader] @@ -74,13 +74,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }); } - protected override APIRequest FetchScores(Action> scoresCallback) + protected abstract APIRequest CreateScoreRequest(); + + protected sealed override APIRequest FetchScores(Action> scoresCallback) { // This performs two requests: - // 1. A request to show the user's score (and scores around). + // 1. A request to show the relevant score (and scores around). // 2. If that fails, a request to index the room starting from the highest score. - var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); + var userScoreReq = CreateScoreRequest(); userScoreReq.Success += userScore => { @@ -111,11 +113,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists setPositions(lowerScores, userScore.Position.Value, 1); } - performSuccessCallback(scoresCallback, allScores); + Schedule(() => PerformSuccessCallback(scoresCallback, allScores)); + hideLoadingSpinners(); }; // On failure, fallback to a normal index. - userScoreReq.Failure += _ => api.Queue(createIndexRequest(scoresCallback)); + userScoreReq.Failure += _ => API.Queue(createIndexRequest(scoresCallback)); return userScoreReq; } @@ -147,8 +150,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private APIRequest createIndexRequest(Action> scoresCallback, MultiplayerScores? pivot = null) { var indexReq = pivot != null - ? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params) - : new IndexPlaylistScoresRequest(roomId, playlistItem.ID); + ? new IndexPlaylistScoresRequest(RoomId, PlaylistItem.ID, pivot.Cursor, pivot.Params) + : new IndexPlaylistScoresRequest(RoomId, PlaylistItem.ID); indexReq.Success += r => { @@ -163,7 +166,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists setPositions(r, pivot, -1); } - performSuccessCallback(scoresCallback, r.Scores, r); + Schedule(() => + { + PerformSuccessCallback(scoresCallback, r.Scores, r); + hideLoadingSpinners(pivot); + }); }; indexReq.Failure += _ => hideLoadingSpinners(pivot); @@ -177,26 +184,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// The callback to invoke with the final s. /// The s that were retrieved from s. /// An optional pivot around which the scores were retrieved. - private void performSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) => Schedule(() => + protected virtual ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { - var scoreInfos = scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); + var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) - { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } + // Invoke callback to add the scores. + callback.Invoke(scoreInfos); - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); - - hideLoadingSpinners(pivot); - }); + return scoreInfos; + } private void hideLoadingSpinners(MultiplayerScores? pivot = null) { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs new file mode 100644 index 0000000000..831b6538a7 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + /// + /// Shows a selected arbitrary score for a playlist item, with scores around included. + /// + public partial class PlaylistItemScoreResultsScreen : PlaylistItemResultsScreen + { + private readonly long scoreId; + + public PlaylistItemScoreResultsScreen(long roomId, PlaylistItem playlistItem, long scoreId) + : base(null, roomId, playlistItem) + { + this.scoreId = scoreId; + } + + protected override APIRequest CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, scoreId); + + protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) + { + var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); + + Schedule(() => SelectedScore.Value = scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); + + return scoreInfos; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs new file mode 100644 index 0000000000..e038cf3288 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + /// + /// Shows the user's best score for a given playlist item, with scores around included. + /// + public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen + { + public PlaylistItemUserResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) + : base(score, roomId, playlistItem) + { + } + + protected override APIRequest CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); + + protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) + { + var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); + + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) + { + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == API.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); + } + + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); + + return scoreInfos; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 48f63731e1..4a2d8f8f6b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(Room.RoomID.Value != null); - return new PlaylistsResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) + return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value.Value, PlaylistItem) { AllowRetry = true, ShowUserStatistics = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 3fb9de428a..3126bbf2eb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RequestResults = item => { Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item)); + ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, RoomId.Value.Value, item)); } } }, From 8e8909c999b3a7a3df67f96b338eb7da03f68fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jul 2024 09:24:03 +0200 Subject: [PATCH 068/670] Adjust daily challenge screen background colour --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 381c713233..dedfdecf2e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -9,7 +9,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -161,7 +160,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"3e3a44") // Temporary. + Colour = colourProvider.Background4, }, new GridContainer { @@ -277,7 +276,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"28242d") // Temporary. + Colour = colourProvider.Background5, }, footerButtons = new FillFlowContainer { From 5fa586848d81a03251798b00fb702ed8cb7f4c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Jun 2024 11:06:22 +0200 Subject: [PATCH 069/670] Replace old bad daily challenge leaderboard with new implementation - Actually shows scores rather than playlist aggregates (which are useful... in playlists, where there is more than one item) - Actually allows scores to be shown by clicking on them - Doesn't completely break down visually on smaller window sizes The general appearance is not as polished as the old one in details but I wanted something quick that we can get out by next weekend. Also includes the naive method of refetching scores once a new top 50 score is detected. I can add a stagger if required. --- .../TestSceneDailyChallengeLeaderboard.cs | 142 ++++++++++++++ .../SongSelect/TestSceneLeaderboardScoreV2.cs | 82 +++++--- .../DailyChallenge/DailyChallenge.cs | 33 ++-- .../DailyChallengeLeaderboard.cs | 175 ++++++++++++++++++ .../Leaderboards/LeaderboardScoreV2.cs | 43 +++-- .../OnlinePlay/TestRoomRequestsHandler.cs | 48 +++++ 6 files changed, 459 insertions(+), 64 deletions(-) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs new file mode 100644 index 0000000000..5fff6bb010 --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeLeaderboard.cs @@ -0,0 +1,142 @@ +// 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.Utils; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osuTK; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeLeaderboard : OsuTestScene + { + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Test] + public void TestBasicBehaviour() + { + DailyChallengeLeaderboard leaderboard = null!; + + AddStep("set up response without user best", () => + { + dummyAPI.HandleRequest = req => + { + if (req is IndexPlaylistScoresRequest indexRequest) + { + indexRequest.TriggerSuccess(createResponse(50, false)); + return true; + } + + return false; + }; + }); + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.8f), + }); + + AddStep("set up response with user best", () => + { + dummyAPI.HandleRequest = req => + { + if (req is IndexPlaylistScoresRequest indexRequest) + { + indexRequest.TriggerSuccess(createResponse(50, true)); + return true; + } + + return false; + }; + }); + AddStep("force refetch", () => leaderboard.RefetchScores()); + } + + [Test] + public void TestLoadingBehaviour() + { + IndexPlaylistScoresRequest pendingRequest = null!; + DailyChallengeLeaderboard leaderboard = null!; + + AddStep("set up requests handler", () => + { + dummyAPI.HandleRequest = req => + { + if (req is IndexPlaylistScoresRequest indexRequest) + { + pendingRequest = indexRequest; + return true; + } + + return false; + }; + }); + AddStep("create leaderboard", () => Child = leaderboard = new DailyChallengeLeaderboard(new Room { RoomID = { Value = 1 } }, new PlaylistItem(Beatmap.Value.BeatmapInfo)) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.8f), + }); + AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(3, true))); + AddStep("force refetch", () => leaderboard.RefetchScores()); + AddStep("complete load", () => pendingRequest.TriggerSuccess(createResponse(4, true))); + } + + private IndexedMultiplayerScores createResponse(int scoreCount, bool returnUserBest) + { + var result = new IndexedMultiplayerScores(); + + for (int i = 0; i < scoreCount; ++i) + { + result.Scores.Add(new MultiplayerScore + { + ID = i, + Accuracy = 1 - (float)i / (2 * scoreCount), + Position = i + 1, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = (ScoreRank)RNG.Next((int)ScoreRank.D, (int)ScoreRank.XH), + MaxCombo = 1000 - i, + TotalScore = (long)(1_000_000 * (1 - (float)i / (2 * scoreCount))), + User = new APIUser { Username = $"user {i}" }, + Statistics = new Dictionary() + }); + } + + if (returnUserBest) + { + result.UserScore = new MultiplayerScore + { + ID = 99999, + Accuracy = 0.91, + Position = 4, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.A, + MaxCombo = 100, + TotalScore = 800000, + User = dummyAPI.LocalUser.Value, + Statistics = new Dictionary() + }; + } + + return result; + } + } +} diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs index 0f5eb06df7..33af4907a1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs @@ -50,35 +50,73 @@ namespace osu.Game.Tests.Visual.SongSelect }); } - [SetUp] - public void Setup() => Schedule(() => + [Test] + public void TestSheared() { - Children = new Drawable[] + AddStep("create content", () => { - fillFlow = new FillFlowContainer + Children = new Drawable[] { - Width = relativeWidth, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 2f), - Shear = new Vector2(OsuGame.SHEAR, 0) - }, - drawWidthText = new OsuSpriteText(), - }; + fillFlow = new FillFlowContainer + { + Width = relativeWidth, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 2f), + Shear = new Vector2(OsuGame.SHEAR, 0) + }, + drawWidthText = new OsuSpriteText(), + }; - foreach (var scoreInfo in getTestScores()) + foreach (var scoreInfo in getTestScores()) + { + fillFlow.Add(new LeaderboardScoreV2(scoreInfo) + { + Rank = scoreInfo.Position, + IsPersonalBest = scoreInfo.User.Id == 2, + Shear = Vector2.Zero, + }); + } + + foreach (var score in fillFlow.Children) + score.Show(); + }); + } + + [Test] + public void TestNonSheared() + { + AddStep("create content", () => { - fillFlow.Add(new LeaderboardScoreV2(scoreInfo, scoreInfo.Position, scoreInfo.User.Id == 2) + Children = new Drawable[] { - Shear = Vector2.Zero, - }); - } + fillFlow = new FillFlowContainer + { + Width = relativeWidth, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 2f), + }, + drawWidthText = new OsuSpriteText(), + }; - foreach (var score in fillFlow.Children) - score.Show(); - }); + foreach (var scoreInfo in getTestScores()) + { + fillFlow.Add(new LeaderboardScoreV2(scoreInfo) + { + Rank = scoreInfo.Position, + IsPersonalBest = scoreInfo.User.Id == 2, + }); + } + + foreach (var score in fillFlow.Children) + score.Show(); + }); + } [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index dedfdecf2e..2d58b3b82c 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private readonly Bindable> userMods = new Bindable>(Array.Empty()); private OnlinePlayScreenWaveContainer waves = null!; - private MatchLeaderboard leaderboard = null!; + private DailyChallengeLeaderboard leaderboard = null!; private RoomModSelectOverlay userModsSelectOverlay = null!; private Sample? sampleStart; private IDisposable? userModsSelectOverlayRegistration; @@ -208,33 +208,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge feed = new DailyChallengeEventFeed { RelativeSizeAxes = Axes.Both, - PresentScore = id => - { - if (this.IsCurrentScreen()) - this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id)); - } + PresentScore = presentScore } ], }, }, null, // Middle column (leaderboard) - new GridContainer + leaderboard = new DailyChallengeLeaderboard(room, playlistItem) { RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new SectionHeader("Leaderboard") - }, - [leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }], - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } + PresentScore = presentScore, }, // Spacer null, @@ -330,6 +314,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; } + private void presentScore(long id) + { + if (this.IsCurrentScreen()) + this.Push(new PlaylistItemScoreResultsScreen(room.RoomID.Value!.Value, playlistItem, id)); + } + private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) { if (e.RoomID != room.RoomID.Value || e.PlaylistItemID != playlistItem.ID) @@ -351,6 +341,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { breakdown.AddNewScore(ev); feed.AddNewScore(ev); + + if (e.NewRank <= 50) + Schedule(() => leaderboard.RefetchScores()); }); }); } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs new file mode 100644 index 0000000000..4d4ae755fc --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.SelectV2.Leaderboards; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeLeaderboard : CompositeDrawable + { + public Action? PresentScore { get; init; } + + private readonly Room room; + private readonly PlaylistItem playlistItem; + + private FillFlowContainer scoreFlow = null!; + private Container userBestContainer = null!; + private SectionHeader userBestHeader = null!; + private LoadingLayer loadingLayer = null!; + + private CancellationTokenSource? cancellationTokenSource; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + public DailyChallengeLeaderboard(Room room, PlaylistItem playlistItem) + { + this.room = room; + this.playlistItem = playlistItem; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + ], + Content = new[] + { + new Drawable[] { new SectionHeader("Leaderboard") }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = scoreFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 20, }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Scale = new Vector2(0.8f), + Width = 1 / 0.8f, + } + }, + loadingLayer = new LoadingLayer + { + RelativeSizeAxes = Axes.Both, + }, + } + } + }, + new Drawable[] { userBestHeader = new SectionHeader("Personal best") { Alpha = 0, } }, + new Drawable[] + { + userBestContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 20, }, + Scale = new Vector2(0.8f), + Width = 1 / 0.8f, + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + RefetchScores(); + } + + public void RefetchScores() + { + var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); + + request.Success += req => + { + var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray(); + var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); + + cancellationTokenSource?.Cancel(); + cancellationTokenSource = null; + cancellationTokenSource ??= new CancellationTokenSource(); + + if (best.Length == 0) + { + scoreFlow.Clear(); + loadingLayer.Hide(); + } + else + { + LoadComponentsAsync(best.Select(s => new LeaderboardScoreV2(s, sheared: false) + { + Rank = s.Position, + IsPersonalBest = s.UserID == api.LocalUser.Value.Id, + Action = () => PresentScore?.Invoke(s.OnlineID), + }), loaded => + { + scoreFlow.Clear(); + scoreFlow.AddRange(loaded); + scoreFlow.FadeTo(1, 400, Easing.OutQuint); + loadingLayer.Hide(); + }, cancellationTokenSource.Token); + } + + userBestContainer.Clear(); + + if (userBest != null) + { + userBestContainer.Add(new LeaderboardScoreV2(userBest, sheared: false) + { + Rank = userBest.Position, + IsPersonalBest = true, + Action = () => PresentScore?.Invoke(userBest.OnlineID), + }); + } + + userBestHeader.FadeTo(userBest == null ? 0 : 1); + }; + + loadingLayer.Show(); + scoreFlow.FadeTo(0.5f, 400, Easing.OutQuint); + api.Queue(request); + } + } +} diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index 804a9d24b7..700f889d7f 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -43,6 +43,9 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { + public int? Rank { get; init; } + public bool IsPersonalBest { get; init; } + private const float expanded_right_content_width = 210; private const float grade_width = 40; private const float username_min_width = 125; @@ -52,15 +55,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards private const float rank_label_visibility_width_cutoff = rank_label_width + height + username_min_width + statistics_regular_min_width + expanded_right_content_width; private readonly ScoreInfo score; + private readonly bool sheared; private const int height = 60; private const int corner_radius = 10; private const int transition_duration = 200; - private readonly int? rank; - - private readonly bool isPersonalBest; - private Colour4 foregroundColour; private Colour4 backgroundColour; private ColourInfo totalScoreBackgroundGradient; @@ -104,13 +104,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); public virtual ScoreInfo TooltipContent => score; - public LeaderboardScoreV2(ScoreInfo score, int? rank, bool isPersonalBest = false) + public LeaderboardScoreV2(ScoreInfo score, bool sheared = true) { this.score = score; - this.rank = rank; - this.isPersonalBest = isPersonalBest; + this.sheared = sheared; - Shear = new Vector2(OsuGame.SHEAR, 0); + Shear = new Vector2(sheared ? OsuGame.SHEAR : 0, 0); RelativeSizeAxes = Axes.X; Height = height; } @@ -120,8 +119,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { var user = score.User; - foregroundColour = isPersonalBest ? colourProvider.Background1 : colourProvider.Background5; - backgroundColour = isPersonalBest ? colourProvider.Background2 : colourProvider.Background4; + foregroundColour = IsPersonalBest ? colourProvider.Background1 : colourProvider.Background5; + backgroundColour = IsPersonalBest ? colourProvider.Background2 : colourProvider.Background4; totalScoreBackgroundGradient = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour); statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s, score) @@ -159,7 +158,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, - Child = rankLabel = new RankLabel(rank) + Child = rankLabel = new RankLabel(Rank, sheared) { Width = rank_label_width, RelativeSizeAxes = Axes.Y, @@ -243,7 +242,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { RelativeSizeAxes = Axes.Both, User = score.User, - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Colour = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0.5f), Colour4.FromHex(@"222A27").Opacity(1)), @@ -274,7 +273,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(1.1f), - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), RelativeSizeAxes = Axes.Both, }) { @@ -292,7 +291,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards RelativeSizeAxes = Axes.Both, Colour = Colour4.Black.Opacity(0.5f), }, - new RankLabel(rank) + new RankLabel(Rank, sheared) { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -314,7 +313,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { flagBadgeAndDateContainer = new FillFlowContainer { - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Direction = FillDirection.Horizontal, Spacing = new Vector2(5), AutoSizeAxes = Axes.Both, @@ -338,7 +337,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards nameLabel = new TruncatingSpriteText { RelativeSizeAxes = Axes.X, - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Text = user.Username, Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold) } @@ -354,7 +353,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards Name = @"Statistics container", Padding = new MarginPadding { Right = 40 }, Spacing = new Vector2(25, 0), - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, @@ -412,7 +411,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards }, RankContainer = new Container { - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, @@ -470,7 +469,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards Anchor = Anchor.TopRight, Origin = Anchor.TopRight, UseFullGlyphHeight = false, - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), }, @@ -478,7 +477,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(2f, 0f), @@ -656,14 +655,14 @@ namespace osu.Game.Screens.SelectV2.Leaderboards private partial class RankLabel : Container, IHasTooltip { - public RankLabel(int? rank) + public RankLabel(int? rank, bool sheared) { if (rank >= 1000) TooltipText = $"#{rank:N0}"; Child = new OsuSpriteText { - Shear = new Vector2(-OsuGame.SHEAR, 0), + Shear = new Vector2(sheared ? -OsuGame.SHEAR : 0, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold, italics: true), diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index ef4539ba56..36e256b920 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -99,6 +99,54 @@ namespace osu.Game.Tests.Visual.OnlinePlay }); return true; + case IndexPlaylistScoresRequest roomLeaderboardRequest: + roomLeaderboardRequest.TriggerSuccess(new IndexedMultiplayerScores + { + Scores = + { + new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = 1, + Position = 1, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.S, + MaxCombo = 1000, + TotalScore = 1000000, + User = new APIUser { Username = "best user" }, + Statistics = new Dictionary() + }, + new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = 0.7, + Position = 2, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.B, + MaxCombo = 100, + TotalScore = 200000, + User = new APIUser { Username = "worst user" }, + Statistics = new Dictionary() + }, + }, + UserScore = new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = 0.91, + Position = 4, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.A, + MaxCombo = 100, + TotalScore = 800000, + User = localUser, + Statistics = new Dictionary() + }, + }); + return true; + case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); return true; From ea4e6cf1d7aac04a664e135a9b43251498ad6167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jul 2024 14:39:11 +0200 Subject: [PATCH 070/670] Add test coverage --- .../Editing/TestScenePlacementBlueprint.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index a5681bea4a..c16533126b 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -5,11 +5,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; @@ -102,5 +104,56 @@ namespace osu.Game.Tests.Visual.Editing AddStep("change tool to circle", () => InputManager.Key(Key.Number2)); AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); } + + [Test] + public void TestAutomaticBankAssignment() + { + AddStep("add object with soft bank", () => EditorBeatmap.Add(new HitCircle + { + StartTime = 0, + Samples = + { + new HitSampleInfo(name: HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, volume: 70), + new HitSampleInfo(name: HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT, volume: 70), + } + })); + AddStep("seek to 500", () => EditorClock.Seek(500)); + AddStep("enable automatic bank assignment", () => + { + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.Q); + InputManager.ReleaseKey(Key.LShift); + }); + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + AddAssert("circle has soft bank", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Bank == HitSampleInfo.BANK_SOFT)); + AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); + } + + [Test] + public void TestVolumeIsInheritedFromLastObject() + { + AddStep("add object with soft bank", () => EditorBeatmap.Add(new HitCircle + { + StartTime = 0, + Samples = + { + new HitSampleInfo(name: HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, volume: 70), + } + })); + AddStep("seek to 500", () => EditorClock.Seek(500)); + AddStep("select drum bank", () => + { + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LShift); + }); + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + AddAssert("circle has drum bank", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM)); + AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); + } } } From 72492a79cdedf0ca75da171125f90dce52dbad31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jul 2024 14:40:40 +0200 Subject: [PATCH 071/670] Reduce duplication in new logic --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 24 ++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 84fe1584dd..63e38bf5de 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -156,24 +156,20 @@ namespace osu.Game.Rulesets.Edit comboInformation.UpdateComboInformation(getPreviousHitObject() as IHasComboInformation); } - if (AutomaticBankAssignment) - { - // Take the hitnormal sample of the last hit object - var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (lastHitNormal != null) - HitObject.Samples[0] = lastHitNormal; - } - else - { - // Only inherit the volume from the previous hit object - var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); + var lastHitNormal = getPreviousHitObject()?.Samples?.FirstOrDefault(o => o.Name == HitSampleInfo.HIT_NORMAL); - if (lastHitNormal != null) + if (lastHitNormal != null) + { + if (AutomaticBankAssignment) { + // Take the hitnormal sample of the last hit object + HitObject.Samples[0] = lastHitNormal; + } + else + { + // Only inherit the volume from the previous hit object for (int i = 0; i < HitObject.Samples.Count; i++) - { HitObject.Samples[i] = HitObject.Samples[i].With(newVolume: lastHitNormal.Volume); - } } } } From 08a77bfe38e740d0df364107383a8a1db9117be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jul 2024 15:02:35 +0200 Subject: [PATCH 072/670] Extend test coverage --- .../Editing/TestScenePlacementBlueprint.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index c16533126b..ee2855354a 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -114,10 +114,11 @@ namespace osu.Game.Tests.Visual.Editing Samples = { new HitSampleInfo(name: HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, volume: 70), - new HitSampleInfo(name: HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT, volume: 70), + new HitSampleInfo(name: HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_DRUM, volume: 70), } })); - AddStep("seek to 500", () => EditorClock.Seek(500)); + + AddStep("seek to 500", () => EditorClock.Seek(500)); // previous object is the one at time 0 AddStep("enable automatic bank assignment", () => { InputManager.PressKey(Key.LShift); @@ -127,8 +128,28 @@ namespace osu.Game.Tests.Visual.Editing AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("place circle", () => InputManager.Click(MouseButton.Left)); - AddAssert("circle has soft bank", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Bank == HitSampleInfo.BANK_SOFT)); + AddAssert("circle has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single().Bank, () => Is.EqualTo(HitSampleInfo.BANK_SOFT)); AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); + + AddStep("seek to 250", () => EditorClock.Seek(250)); // previous object is the one at time 0 + AddStep("enable clap addition", () => InputManager.Key(Key.R)); + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2)); + AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, + () => Is.EqualTo(HitSampleInfo.BANK_SOFT)); + AddAssert("clap sample has drum bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank, + () => Is.EqualTo(HitSampleInfo.BANK_DRUM)); + AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); + + AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[3].Samples, () => Has.Count.EqualTo(2)); + AddAssert("all samples have soft bank", () => EditorBeatmap.HitObjects[3].Samples.All(s => s.Bank == HitSampleInfo.BANK_SOFT)); + AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[3].Samples.All(s => s.Volume == 70)); } [Test] From e005b46df97397126220167289143e335002535a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jul 2024 15:19:28 +0200 Subject: [PATCH 073/670] Extend test coverage --- .../Editing/TestScenePlacementBlueprint.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index ee2855354a..e9b442f8dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; @@ -176,5 +177,48 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("circle has drum bank", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM)); AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70)); } + + [Test] + public void TestNodeSamplesAndSamplesAreSame() + { + Playfield playfield = null!; + + AddStep("select drum bank", () => + { + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.R); + InputManager.ReleaseKey(Key.LShift); + }); + AddStep("enable clap addition", () => InputManager.Key(Key.R)); + + AddStep("select slider placement tool", () => InputManager.Key(Key.Number3)); + AddStep("move mouse to top left of playfield", () => + { + playfield = this.ChildrenOfType().Single(); + var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("begin placement", () => InputManager.Click(MouseButton.Left)); + AddStep("move mouse to bottom right of playfield", () => + { + var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("confirm via global action", () => + { + globalActionContainer.TriggerPressed(GlobalAction.Select); + globalActionContainer.TriggerReleased(GlobalAction.Select); + }); + AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); + + AddAssert("slider samples have drum bank", () => EditorBeatmap.HitObjects[0].Samples.All(s => s.Bank == HitSampleInfo.BANK_DRUM)); + AddAssert("slider node samples have drum bank", + () => ((IHasRepeats)EditorBeatmap.HitObjects[0]).NodeSamples.SelectMany(s => s).All(s => s.Bank == HitSampleInfo.BANK_DRUM)); + + AddAssert("slider samples have clap addition", + () => EditorBeatmap.HitObjects[0].Samples.Select(s => s.Name), () => Does.Contain(HitSampleInfo.HIT_CLAP)); + AddAssert("slider node samples have clap addition", + () => ((IHasRepeats)EditorBeatmap.HitObjects[0]).NodeSamples.All(samples => samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))); + } } } From 652d2e963313f53b19b97108c3ad6e454910961c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jul 2024 15:19:36 +0200 Subject: [PATCH 074/670] Adjust code style --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 8a3fac6d3a..2817e26abd 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -183,9 +183,7 @@ namespace osu.Game.Rulesets.Edit { // Make sure all the node samples are identical to the hit object's samples for (int i = 0; i < hasRepeats.NodeSamples.Count; i++) - { hasRepeats.NodeSamples[i] = HitObject.Samples.Select(o => o.With()).ToList(); - } } } From f201cc3feaf9af6bed621d06c761595a9b1eb36e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 10:09:06 +0900 Subject: [PATCH 075/670] Expand explanation in inline comment --- osu.Game/Skinning/BeatmapSkinProvidingContainer.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs index 94c7a3aac6..41fa7fcc66 100644 --- a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -101,8 +101,16 @@ namespace osu.Game.Skinning bool userSkinIsLegacy = skins.CurrentSkin.Value is LegacySkin; bool beatmapProvidingResources = skin is LegacySkinTransformer legacySkin && legacySkin.IsProvidingLegacyResources; - // If the beatmap skin looks to have skinnable resources and the user's skin choice is not a legacy skin, - // add the default classic skin as a fallback opportunity. + // Some beatmaps provide a limited selection of skin elements to add some visual flair. + // In stable, these elements will take lookup priority over the selected skin (whether that be a user skin or default). + // + // To replicate this we need to pay special attention to the fallback order. + // If a user has a non-legacy skin (argon, triangles) selected, the game won't normally fall back to a legacy skin. + // In turn this can create an unexpected visual experience. + // + // So here, check what skin the user has selected. If it's already a legacy skin then we don't need to do anything special. + // If it isn't, we insert the classic default. Note that this is only done if the beatmap seems to be providing skin elements, + // as we only want to override the user's (non-legacy) skin choice when required for beatmap skin visuals. if (!userSkinIsLegacy && beatmapProvidingResources && classicFallback != null) SetSources(new[] { skin, classicFallback }); else From d21eec9542a778d559b2ed679db1489092c8ad91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 11:55:27 +0900 Subject: [PATCH 076/670] Apply nullability to `MusicController` --- osu.Game/Overlays/MusicController.cs | 43 +++++++++++++++------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0986c0513c..ef12d1eba2 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -28,7 +25,7 @@ namespace osu.Game.Overlays public partial class MusicController : CompositeDrawable { [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; /// /// Point in time after which the current track will be restarted on triggering a "previous track" action. @@ -49,25 +46,28 @@ namespace osu.Game.Overlays /// Fired when the global has changed. /// Includes direction information for display purposes. /// - public event Action TrackChanged; + public event Action? TrackChanged; [Resolved] - private IBindable beatmap { get; set; } + private IBindable beatmap { get; set; } = null!; [Resolved] - private IBindable> mods { get; set; } + private IBindable> mods { get; set; } = null!; - [NotNull] public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000)); [Resolved] - private RealmAccess realm { get; set; } + private RealmAccess realm { get; set; } = null!; protected override void LoadComplete() { base.LoadComplete(); - beatmap.BindValueChanged(b => changeBeatmap(b.NewValue), true); + beatmap.BindValueChanged(b => + { + if (b.NewValue != null) + changeBeatmap(b.NewValue); + }, true); mods.BindValueChanged(_ => ResetTrackAdjustments(), true); } @@ -76,6 +76,9 @@ namespace osu.Game.Overlays /// public void ReloadCurrentTrack() { + if (current == null) + return; + changeTrack(); TrackChanged?.Invoke(current, TrackChangeDirection.None); } @@ -90,7 +93,7 @@ namespace osu.Game.Overlays /// public bool TrackLoaded => CurrentTrack.TrackLoaded; - private ScheduledDelegate seekDelegate; + private ScheduledDelegate? seekDelegate; public void SeekTo(double position) { @@ -192,7 +195,7 @@ namespace osu.Game.Overlays /// Play the previous track or restart the current track if it's current time below . /// /// Invoked when the operation has been performed successfully. - public void PreviousTrack(Action onSuccess = null) => Schedule(() => + public void PreviousTrack(Action? onSuccess = null) => Schedule(() => { PreviousTrackResult res = prev(); if (res != PreviousTrackResult.None) @@ -218,7 +221,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current.BeatmapSetInfo)).LastOrDefault() + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault() ?? getBeatmapSets().LastOrDefault(); if (playableSet != null) @@ -236,7 +239,7 @@ namespace osu.Game.Overlays /// /// Invoked when the operation has been performed successfully. /// A of the operation. - public void NextTrack(Action onSuccess = null) => Schedule(() => + public void NextTrack(Action? onSuccess = null) => Schedule(() => { bool res = next(); if (res) @@ -250,7 +253,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current.BeatmapSetInfo)).ElementAtOrDefault(1) + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)).ElementAtOrDefault(1) ?? getBeatmapSets().FirstOrDefault(); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -272,7 +275,7 @@ namespace osu.Game.Overlays Schedule(() => CurrentTrack.RestartAsync()); } - private WorkingBeatmap current; + private WorkingBeatmap? current; private TrackChangeDirection? queuedDirection; @@ -289,7 +292,7 @@ namespace osu.Game.Overlays TrackChangeDirection direction = TrackChangeDirection.None; - bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) == true; + bool audioEquals = newWorking.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) == true; if (current != null) { @@ -304,7 +307,7 @@ namespace osu.Game.Overlays { // figure out the best direction based on order in playlist. int last = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(current.BeatmapSetInfo)).Count(); - int next = newWorking == null ? -1 : getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); + int next = getBeatmapSets().AsEnumerable().TakeWhile(b => !b.Equals(newWorking.BeatmapSetInfo)).Count(); direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next; } @@ -361,7 +364,7 @@ namespace osu.Game.Overlays { // Important to keep this in its own method to avoid inadvertently capturing unnecessary variables in the callback. // Can lead to leaks. - var queuedTrack = new DrawableTrack(current.LoadTrack()); + var queuedTrack = new DrawableTrack(current!.LoadTrack()); queuedTrack.Completed += onTrackCompleted; return queuedTrack; } @@ -390,7 +393,7 @@ namespace osu.Game.Overlays } } - private AudioAdjustments modTrackAdjustments; + private AudioAdjustments? modTrackAdjustments; /// /// Resets the adjustments currently applied on and applies the mod adjustments if is true. From 0696e2df32bd0629f0a1314d9f87fd364074954d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 12:58:18 +0900 Subject: [PATCH 077/670] Apply nullability to ducking methods --- osu.Game/Overlays/MusicController.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 493acb183d..54a0436f2a 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -61,8 +61,9 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } = null!; - private AudioFilter audioDuckFilter; private readonly BindableDouble audioDuckVolume = new BindableDouble(1); + + private AudioFilter? audioDuckFilter; private bool audioDuckActive; [BackgroundDependencyLoader] @@ -268,7 +269,7 @@ namespace osu.Game.Overlays /// Easing for the ducking transition. /// Duration of the unducking transition, in ms. /// Easing for the unducking transition. - public IDisposable Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic) + public IDisposable? Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic) { if (audioDuckActive) return null; @@ -295,7 +296,8 @@ namespace osu.Game.Overlays /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Duration of the ducking transition, in ms. /// Easing for the ducking transition. - public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, Easing duckEasing = Easing.OutCubic) + public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, + Easing duckEasing = Easing.OutCubic) { if (audioDuckActive) return; From 482ac32f0143e25ad780b7a14e9d3ce398aeae24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 13:00:10 +0900 Subject: [PATCH 078/670] Remove and ignore `encodings.xml` Disappeared in latest EAP and doesn't look like something we would have wanted to be committed in the first place. --- .gitignore | 1 + .idea/.idea.osu.Desktop/.idea/encodings.xml | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/encodings.xml diff --git a/.gitignore b/.gitignore index a51ad09d6c..1fec94d82b 100644 --- a/.gitignore +++ b/.gitignore @@ -266,6 +266,7 @@ __pycache__/ .idea/**/dictionaries .idea/**/shelf .idea/*/.idea/projectSettingsUpdater.xml +.idea/*/.idea/encodings.xml # Generated files .idea/**/contentModel.xml diff --git a/.idea/.idea.osu.Desktop/.idea/encodings.xml b/.idea/.idea.osu.Desktop/.idea/encodings.xml deleted file mode 100644 index 15a15b218a..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/encodings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 4528daf7fa5d663996bc4e9eac6368e7b0951e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 13:15:15 +0900 Subject: [PATCH 079/670] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 67c9f2e100..447c783b29 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From ec4623d49f3efec2e06328616d1e2640cc18acc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 13:51:29 +0900 Subject: [PATCH 080/670] Reduce duck length slightly on toolbar ruleset selector --- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index c39ff60b6b..796477f9f6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Toolbar rulesetSelectionChannel[r.NewValue] = channel; channel.Play(); - musicController?.DuckMomentarily(600); + musicController?.DuckMomentarily(500); } public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 0d858ce8f8eea091b40214a2709d5862021aa33b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 13:51:41 +0900 Subject: [PATCH 081/670] Change default easings to `In`/`Out` for all ducking operations --- osu.Game/Overlays/MusicController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 54a0436f2a..94078a3dec 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -269,7 +269,7 @@ namespace osu.Game.Overlays /// Easing for the ducking transition. /// Duration of the unducking transition, in ms. /// Easing for the unducking transition. - public IDisposable? Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.OutCubic, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic) + public IDisposable? Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.Out, int unduckDuration = 500, Easing unduckEasing = Easing.In) { if (audioDuckActive) return null; @@ -296,8 +296,8 @@ namespace osu.Game.Overlays /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. /// Duration of the ducking transition, in ms. /// Easing for the ducking transition. - public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.InCubic, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, - Easing duckEasing = Easing.OutCubic) + public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.In, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, + Easing duckEasing = Easing.Out) { if (audioDuckActive) return; From 554740af1010855598b3f2c40e9dee369438b11b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 14:41:50 +0900 Subject: [PATCH 082/670] Adjust ducking API to use a parameters `record` --- .../Collections/ManageCollectionsDialog.cs | 13 ++- osu.Game/Overlays/DialogOverlay.cs | 13 ++- osu.Game/Overlays/MusicController.cs | 104 ++++++++++++------ 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 0396fd531c..11d50f3ce4 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -24,7 +24,7 @@ namespace osu.Game.Collections protected override string PopInSampleName => @"UI/overlay-big-pop-in"; protected override string PopOutSampleName => @"UI/overlay-big-pop-out"; - private IDisposable? audioDucker; + private IDisposable? duckOperation; [Resolved] private MusicController? musicController { get; set; } @@ -120,12 +120,17 @@ namespace osu.Game.Collections protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - audioDucker?.Dispose(); + duckOperation?.Dispose(); } protected override void PopIn() { - audioDucker = musicController?.Duck(100, 1f, unduckDuration: 100); + duckOperation = musicController?.Duck(new DuckParameters + { + DuckDuration = 100, + DuckVolumeTo = 1, + RestoreDuration = 100, + }); this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); @@ -135,7 +140,7 @@ namespace osu.Game.Collections { base.PopOut(); - audioDucker?.Dispose(); + duckOperation?.Dispose(); this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 7c52081053..97d77ae2d3 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays || dialogContainer.Children.Count > 0; [CanBeNull] - private IDisposable audioDucker; + private IDisposable duckOperation; public DialogOverlay() { @@ -53,7 +53,7 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - audioDucker?.Dispose(); + duckOperation?.Dispose(); } public void Push(PopupDialog dialog) @@ -106,13 +106,18 @@ namespace osu.Game.Overlays protected override void PopIn() { - audioDucker = musicController.Duck(100, 1f, unduckDuration: 100); + duckOperation = musicController?.Duck(new DuckParameters + { + DuckDuration = 100, + DuckVolumeTo = 1, + RestoreDuration = 100, + }); } protected override void PopOut() { base.PopOut(); - audioDucker?.Dispose(); + duckOperation?.Dispose(); // PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present. if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 94078a3dec..cd770bef28 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; @@ -263,59 +262,53 @@ namespace osu.Game.Overlays /// /// Attenuates the volume and/or filters the currently playing track. /// - /// Duration of the ducking transition, in ms. - /// Level to drop volume to (1.0 = 100%). - /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. - /// Easing for the ducking transition. - /// Duration of the unducking transition, in ms. - /// Easing for the unducking transition. - public IDisposable? Duck(int duration = 0, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, Easing easing = Easing.Out, int unduckDuration = 500, Easing unduckEasing = Easing.In) + public IDisposable? Duck(DuckParameters? parameters = null) { + parameters ??= new DuckParameters(); + if (audioDuckActive) return null; audioDuckActive = true; Schedule(() => { - if (duckCutoffTo.IsNotNull()) - audioDuckFilter?.CutoffTo((int)duckCutoffTo, duration, easing); + if (parameters.DuckCutoffTo != null) + audioDuckFilter?.CutoffTo(parameters.DuckCutoffTo.Value, parameters.DuckDuration, parameters.DuckEasing); - this.TransformBindableTo(audioDuckVolume, duckVolumeTo, duration, easing); + this.TransformBindableTo(audioDuckVolume, parameters.DuckVolumeTo, parameters.DuckDuration, parameters.DuckEasing); }); - return new InvokeOnDisposal(() => unduck(unduckDuration, unduckEasing)); + return new InvokeOnDisposal(restoreDucking); + + void restoreDucking() + { + if (!audioDuckActive) return; + + audioDuckActive = false; + + Schedule(() => + { + audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, parameters.RestoreDuration, parameters.RestoreEasing); + this.TransformBindableTo(audioDuckVolume, 1, parameters.RestoreDuration, parameters.RestoreEasing); + }); + } } /// - /// A convenience method that ducks the currently playing track, then after a delay, unducks it. + /// A convenience method that ducks the currently playing track, then after a delay, restores automatically. /// - /// Delay after audio is ducked before unducking begins, in ms. - /// Duration of the unducking transition, in ms. - /// Easing for the unducking transition. - /// Level to drop volume to (1.0 = 100%). - /// Cutoff frequency to drop `AudioFilter` to. Use `null` to skip filter effect. - /// Duration of the ducking transition, in ms. - /// Easing for the ducking transition. - public void DuckMomentarily(int delay, int unduckDuration = 500, Easing unduckEasing = Easing.In, float duckVolumeTo = 0.25f, int? duckCutoffTo = 300, int duckDuration = 0, - Easing duckEasing = Easing.Out) + /// A delay in milliseconds which defines how long to delay restoration after ducking completes. + /// Parameters defining the ducking operation. + public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null) { - if (audioDuckActive) return; + parameters ??= new DuckParameters(); - Duck(duckDuration, duckVolumeTo, duckCutoffTo, duckEasing); - Scheduler.AddDelayed(() => unduck(unduckDuration, unduckEasing), delay); - } + IDisposable? duckOperation = Duck(parameters); - private void unduck(int duration, Easing easing) - { - if (!audioDuckActive) return; + if (duckOperation == null) + return; - audioDuckActive = false; - - Schedule(() => - { - audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, duration, easing); - this.TransformBindableTo(audioDuckVolume, 1, duration, easing); - }); + Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore); } private bool next() @@ -491,6 +484,45 @@ namespace osu.Game.Overlays } } + public record DuckParameters + { + /// + /// The duration of the ducking transition in milliseconds. + /// Defaults to no duration (immediate ducking). + /// + public double DuckDuration = 0; + + /// + /// The final volume which should be reached during ducking, when 0 is silent and 1 is original volume. + /// Defaults to 25%. + /// + public float DuckVolumeTo = 0.25f; + + /// + /// The low-pass cutoff frequency which should be reached during ducking. Use `null` to skip filter effect. + /// Defaults to 300 Hz. + /// + public int? DuckCutoffTo = 300; + + /// + /// The easing curve to be applied during ducking. + /// Defaults to . + /// + public Easing DuckEasing = Easing.Out; + + /// + /// The duration of the restoration transition in milliseconds. + /// Defaults to 500 ms. + /// + public double RestoreDuration = 500; + + /// + /// The easing curve to be applied during restoration. + /// Defaults to . + /// + public Easing RestoreEasing = Easing.In; + } + public enum TrackChangeDirection { None, From 20ba6ca867477ad45ab296013fdda94ea820ba3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 15:11:11 +0900 Subject: [PATCH 083/670] Add mention of return type for `Duck` method --- osu.Game/Overlays/MusicController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index cd770bef28..fb8ba988a7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -262,6 +262,7 @@ namespace osu.Game.Overlays /// /// Attenuates the volume and/or filters the currently playing track. /// + /// A which will restore the duck operation when disposed, or null if another duck operation was already in progress. public IDisposable? Duck(DuckParameters? parameters = null) { parameters ??= new DuckParameters(); From d6c1b5d3991a4e9187e85806697b7f3b1e49206b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 5 Jul 2024 08:54:47 +0200 Subject: [PATCH 084/670] Add failing test coverage --- .../TestSceneHitObjectSampleAdjustments.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 9988c1cb59..558d8dce94 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -484,6 +484,25 @@ namespace osu.Game.Tests.Visual.Editing hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); } + [Test] + public void TestSelectingObjectDoesNotMutateSamples() + { + clickSamplePiece(0); + toggleAdditionViaPopover(1); + setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT); + dismissPopover(); + + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + + AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0])); + + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + } + private void clickSamplePiece(int objectIndex) => AddStep($"click {objectIndex.ToOrdinalWords()} sample piece", () => { var samplePiece = this.ChildrenOfType().Single(piece => piece.HitObject == EditorBeatmap.HitObjects.ElementAt(objectIndex)); From 4c59ec1d94489857b12705a2d195b59aed019f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 5 Jul 2024 09:10:38 +0200 Subject: [PATCH 085/670] Fix incorrect ternary state computation for bank toggles Closes https://github.com/ppy/osu/issues/28741. Regressed in a7b066f3ee59b9e9f13344ce3af4c5e7cf511e67. The intent of the original change there was to ensure that addition banks being set will put the ternary state toggles in indeterminate state (to at least provide a visual indication that the selection does not use a single bank). This would previously not be the case due to the use of `.All()` in the original condition (a single object/node was considered to have a bank enabled if and only if *all* samples within it used it). However the attempt to fix that via switching to `Any()` was not correct. The logic used in the offending commit operates on extracted `Samples` and `NodeSamples` from the selection, and would consider the ternary toggle: - fully off if none of the samples/node samples contained a sample with the given bank, - indeterminate if the some of the samples/node samples contained a sample with the given bank, - fully on if at least one sample from every samples/node samples contained a sample with the given bank. This is a *two-tiered* process, as in first a *binary* on/off state is extracted from each object's samples/node samples, and *then* a ternary state is extracted from all objects/nodes. This is insufficient to express the *desired* behaviour, which is that the toggle should be: - fully off if *none of the individual samples in the selection* use the given bank, - indeterminate if *at least one individual sample in the selection* uses the given bank, - fully on if *all individual samples in the selection* use the given bank. The second wording is flattened, and no longer tries to consider "nodes" or "objects", it just looks at all of the samples in the selection without concern as to whether they're from separate objects/nodes or not. To explain why this discrepancy caused the bug, consider a single object with a `soft` normal bank and `drum` addition bank. Selecting the object would cause a ternary button state update; as per the incorrect logic, there were two samples on the object and each had its own separate banks, so two ternary toggles would have their state set to `True` (rather than the correct `Indeterminate`), thus triggering a bindable feedback loop that would cause one of these banks to win and actually overwrite the other. Note that the addition indeterminate state computation *still* needs to do the two-tiered process, because there it actually makes sense (for a selection to have an addition fully on rather than indeterminate, *every* object/node *must* contain that addition). --- .../Screens/Edit/Compose/Components/EditorSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index 70c91b16fd..a4efe66bf8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit.Compose.Components foreach ((string bankName, var bindable) in SelectionBankStates) { - bindable.Value = GetStateFromSelection(samplesInSelection, h => h.Any(s => s.Bank == bankName)); + bindable.Value = GetStateFromSelection(samplesInSelection.SelectMany(s => s), h => h.Bank == bankName); } IEnumerable> enumerateAllSamples(HitObject hitObject) From 717f7ba9f01d0f2ec8a78c71b86c8433c1f8b2c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 18:12:40 +0900 Subject: [PATCH 086/670] Better support multiple concurrent ducking operations --- osu.Game/Overlays/MusicController.cs | 56 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index fb8ba988a7..637fcb57e2 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -62,8 +63,7 @@ namespace osu.Game.Overlays private readonly BindableDouble audioDuckVolume = new BindableDouble(1); - private AudioFilter? audioDuckFilter; - private bool audioDuckActive; + private AudioFilter audioDuckFilter = null!; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -259,40 +259,41 @@ namespace osu.Game.Overlays onSuccess?.Invoke(); }); + private readonly List duckOperations = new List(); + /// - /// Attenuates the volume and/or filters the currently playing track. + /// Applies ducking, attenuating the volume and/or low-pass cutoff of the currently playing track to make headroom for effects (or just to apply an effect). /// - /// A which will restore the duck operation when disposed, or null if another duck operation was already in progress. - public IDisposable? Duck(DuckParameters? parameters = null) + /// A which will restore the duck operation when disposed. + public IDisposable Duck(DuckParameters? parameters = null) { parameters ??= new DuckParameters(); - if (audioDuckActive) return null; + if (duckOperations.Contains(parameters)) + throw new ArgumentException("Ducking has already been applied for the provided parameters.", nameof(parameters)); - audioDuckActive = true; + duckOperations.Add(parameters); - Schedule(() => - { - if (parameters.DuckCutoffTo != null) - audioDuckFilter?.CutoffTo(parameters.DuckCutoffTo.Value, parameters.DuckDuration, parameters.DuckEasing); + DuckParameters volumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo)!; + DuckParameters lowPassOperation = duckOperations.MinBy(p => p.DuckCutoffTo)!; - this.TransformBindableTo(audioDuckVolume, parameters.DuckVolumeTo, parameters.DuckDuration, parameters.DuckEasing); - }); + audioDuckFilter.CutoffTo(lowPassOperation.DuckCutoffTo, lowPassOperation.DuckDuration, lowPassOperation.DuckEasing); + this.TransformBindableTo(audioDuckVolume, volumeOperation.DuckVolumeTo, volumeOperation.DuckDuration, volumeOperation.DuckEasing); return new InvokeOnDisposal(restoreDucking); - void restoreDucking() + void restoreDucking() => Schedule(() => { - if (!audioDuckActive) return; + Debug.Assert(duckOperations.Contains(parameters)); + duckOperations.Remove(parameters); - audioDuckActive = false; + DuckParameters? restoreVolumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo); + DuckParameters? restoreLowPassOperation = duckOperations.MinBy(p => p.DuckCutoffTo); - Schedule(() => - { - audioDuckFilter?.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, parameters.RestoreDuration, parameters.RestoreEasing); - this.TransformBindableTo(audioDuckVolume, 1, parameters.RestoreDuration, parameters.RestoreEasing); - }); - } + // If another duck operation is in the list, restore ducking to its level, else reset back to defaults. + audioDuckFilter.CutoffTo(restoreLowPassOperation?.DuckCutoffTo ?? AudioFilter.MAX_LOWPASS_CUTOFF, parameters.RestoreDuration, parameters.RestoreEasing); + this.TransformBindableTo(audioDuckVolume, restoreVolumeOperation?.DuckVolumeTo ?? 1, parameters.RestoreDuration, parameters.RestoreEasing); + }); } /// @@ -304,10 +305,7 @@ namespace osu.Game.Overlays { parameters ??= new DuckParameters(); - IDisposable? duckOperation = Duck(parameters); - - if (duckOperation == null) - return; + IDisposable duckOperation = Duck(parameters); Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore); } @@ -485,7 +483,7 @@ namespace osu.Game.Overlays } } - public record DuckParameters + public class DuckParameters { /// /// The duration of the ducking transition in milliseconds. @@ -500,10 +498,10 @@ namespace osu.Game.Overlays public float DuckVolumeTo = 0.25f; /// - /// The low-pass cutoff frequency which should be reached during ducking. Use `null` to skip filter effect. + /// The low-pass cutoff frequency which should be reached during ducking. If not required, set to . /// Defaults to 300 Hz. /// - public int? DuckCutoffTo = 300; + public int DuckCutoffTo = 300; /// /// The easing curve to be applied during ducking. From 65418aca1adf4ec167bb0ea3f27cad016daa4ac5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 18:27:46 +0900 Subject: [PATCH 087/670] Add basic test coverage --- .../Visual/Menus/TestAudioDucking.cs | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestAudioDucking.cs diff --git a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs new file mode 100644 index 0000000000..1639912d29 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Audio.Effects; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneAudioDucking : OsuGameTestScene + { + [Test] + public void TestMomentaryDuck() + { + AddStep("duck momentarily", () => Game.MusicController.DuckMomentarily(1000, new DuckParameters + { + DuckDuration = 300, + })); + } + + [Test] + public void TestMultipleDucks() + { + IDisposable duckOp1 = null!; + IDisposable duckOp2 = null!; + + double normalVolume = 1; + + AddStep("get initial volume", () => + { + normalVolume = Game.Audio.Tracks.AggregateVolume.Value; + }); + + AddStep("duck one", () => + { + duckOp1 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 0.5f, + }); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + + AddStep("duck two", () => + { + duckOp2 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 0.2f, + }); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + + AddStep("restore two", () => duckOp2.Dispose()); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + + AddStep("restore one", () => duckOp1.Dispose()); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(float.Epsilon)); + } + + [Test] + public void TestMultipleDucksReverseOrder() + { + IDisposable duckOp1 = null!; + IDisposable duckOp2 = null!; + + double normalVolume = 1; + + AddStep("get initial volume", () => + { + normalVolume = Game.Audio.Tracks.AggregateVolume.Value; + }); + + AddStep("duck one", () => + { + duckOp1 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 0.5f, + }); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + + AddStep("duck two", () => + { + duckOp2 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 0.2f, + }); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + + AddStep("restore one", () => duckOp1.Dispose()); + + // reverse order, less extreme duck removed so won't change + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + + AddStep("restore two", () => duckOp2.Dispose()); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(float.Epsilon)); + } + + [Test] + public void TestMultipleDucksDifferentPieces() + { + IDisposable duckOp1 = null!; + IDisposable duckOp2 = null!; + + AddStep("duck volume", () => + { + duckOp1 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 0.2f, + DuckCutoffTo = AudioFilter.MAX_LOWPASS_CUTOFF, + DuckDuration = 500, + }); + }); + + AddStep("duck lowpass", () => + { + duckOp2 = Game.MusicController.Duck(new DuckParameters + { + DuckVolumeTo = 1, + DuckCutoffTo = 300, + DuckDuration = 500, + }); + }); + + AddStep("restore lowpass", () => duckOp2.Dispose()); + AddStep("restore volume", () => duckOp1.Dispose()); + } + } +} From 7efb4ce30a0d50a16503ce0a75d24db5a961be75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 18:36:40 +0900 Subject: [PATCH 088/670] Fix multiple disposals resulting in assert being hit --- osu.Game.Tests/Visual/Menus/TestAudioDucking.cs | 10 ++++++++++ osu.Game/Overlays/MusicController.cs | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs index 1639912d29..e704fc0e83 100644 --- a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs +++ b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs @@ -101,6 +101,16 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(float.Epsilon)); } + [Test] + public void TestMultipleDisposalIsNoop() + { + IDisposable duckOp1 = null!; + + AddStep("duck", () => duckOp1 = Game.MusicController.Duck()); + AddStep("restore", () => duckOp1.Dispose()); + AddStep("restore", () => duckOp1.Dispose()); + } + [Test] public void TestMultipleDucksDifferentPieces() { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 637fcb57e2..8fa77b6153 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -284,8 +283,8 @@ namespace osu.Game.Overlays void restoreDucking() => Schedule(() => { - Debug.Assert(duckOperations.Contains(parameters)); - duckOperations.Remove(parameters); + if (!duckOperations.Remove(parameters)) + return; DuckParameters? restoreVolumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo); DuckParameters? restoreLowPassOperation = duckOperations.MinBy(p => p.DuckCutoffTo); From 5907e0d1eb590dfbd55f79843dd31a041d78f3c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 5 Jul 2024 18:38:24 +0900 Subject: [PATCH 089/670] Make `DuckDuration` non-zero by default --- osu.Game.Tests/Visual/Menus/TestAudioDucking.cs | 5 +---- osu.Game/Collections/ManageCollectionsDialog.cs | 2 +- osu.Game/Overlays/DialogOverlay.cs | 2 +- osu.Game/Overlays/MusicController.cs | 4 ++-- osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs index e704fc0e83..459bdd3e2e 100644 --- a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs +++ b/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs @@ -13,10 +13,7 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestMomentaryDuck() { - AddStep("duck momentarily", () => Game.MusicController.DuckMomentarily(1000, new DuckParameters - { - DuckDuration = 300, - })); + AddStep("duck momentarily", () => Game.MusicController.DuckMomentarily(1000)); } [Test] diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 11d50f3ce4..9f8158af53 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -127,8 +127,8 @@ namespace osu.Game.Collections { duckOperation = musicController?.Duck(new DuckParameters { - DuckDuration = 100, DuckVolumeTo = 1, + DuckDuration = 100, RestoreDuration = 100, }); diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 97d77ae2d3..4e7aff84bc 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -108,8 +108,8 @@ namespace osu.Game.Overlays { duckOperation = musicController?.Duck(new DuckParameters { - DuckDuration = 100, DuckVolumeTo = 1, + DuckDuration = 100, RestoreDuration = 100, }); } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8fa77b6153..c4817a8f26 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -486,9 +486,9 @@ namespace osu.Game.Overlays { /// /// The duration of the ducking transition in milliseconds. - /// Defaults to no duration (immediate ducking). + /// Defaults to 100 ms. /// - public double DuckDuration = 0; + public double DuckDuration = 100; /// /// The final volume which should be reached during ducking, when 0 is silent and 1 is original volume. diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 796477f9f6..05ab505417 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Toolbar rulesetSelectionChannel[r.NewValue] = channel; channel.Play(); - musicController?.DuckMomentarily(500); + musicController?.DuckMomentarily(500, new DuckParameters { DuckDuration = 0 }); } public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; From 2bfa03c6d83b321bedcb6a2b659b692548f0490a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 07:37:42 +0300 Subject: [PATCH 090/670] Rename test scene file --- .../Menus/{TestAudioDucking.cs => TestSceneAudioDucking.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Menus/{TestAudioDucking.cs => TestSceneAudioDucking.cs} (100%) diff --git a/osu.Game.Tests/Visual/Menus/TestAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs similarity index 100% rename from osu.Game.Tests/Visual/Menus/TestAudioDucking.cs rename to osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs From 0067450b226d06d5812e4d836a64e017ba049727 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 13:47:04 +0900 Subject: [PATCH 091/670] Change volume parameter to `double` --- osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs | 10 +++++----- osu.Game/Overlays/MusicController.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs index 459bdd3e2e..20549018e0 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Menus { duckOp1 = Game.MusicController.Duck(new DuckParameters { - DuckVolumeTo = 0.5f, + DuckVolumeTo = 0.5, }); }); @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus { duckOp2 = Game.MusicController.Duck(new DuckParameters { - DuckVolumeTo = 0.2f, + DuckVolumeTo = 0.2, }); }); @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Menus { duckOp1 = Game.MusicController.Duck(new DuckParameters { - DuckVolumeTo = 0.5f, + DuckVolumeTo = 0.5, }); }); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Menus { duckOp2 = Game.MusicController.Duck(new DuckParameters { - DuckVolumeTo = 0.2f, + DuckVolumeTo = 0.2, }); }); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.Menus { duckOp1 = Game.MusicController.Duck(new DuckParameters { - DuckVolumeTo = 0.2f, + DuckVolumeTo = 0.2, DuckCutoffTo = AudioFilter.MAX_LOWPASS_CUTOFF, DuckDuration = 500, }); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index c4817a8f26..a01f3bfa5f 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -494,7 +494,7 @@ namespace osu.Game.Overlays /// The final volume which should be reached during ducking, when 0 is silent and 1 is original volume. /// Defaults to 25%. /// - public float DuckVolumeTo = 0.25f; + public double DuckVolumeTo = 0.25; /// /// The low-pass cutoff frequency which should be reached during ducking. If not required, set to . From aa36a844be987d50ff1fe4a79645fc65e3eb3441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 13:49:25 +0900 Subject: [PATCH 092/670] Reduce precision requirement for tests --- .../Visual/Menus/TestSceneAudioDucking.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs index 20549018e0..a7ec7f5bdc 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Menus }); }); - AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); AddStep("duck two", () => { @@ -47,13 +47,13 @@ namespace osu.Game.Tests.Visual.Menus }); }); - AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01)); AddStep("restore two", () => duckOp2.Dispose()); - AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); AddStep("restore one", () => duckOp1.Dispose()); - AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(float.Epsilon)); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01)); } [Test] @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Menus }); }); - AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(float.Epsilon)); + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); AddStep("duck two", () => { @@ -87,15 +87,15 @@ namespace osu.Game.Tests.Visual.Menus }); }); - AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01)); AddStep("restore one", () => duckOp1.Dispose()); // reverse order, less extreme duck removed so won't change - AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(float.Epsilon)); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01)); AddStep("restore two", () => duckOp2.Dispose()); - AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(float.Epsilon)); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01)); } [Test] From 3650f3c47978244befd871e7ae705ae033d50288 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 13:52:40 +0900 Subject: [PATCH 093/670] Allow multiple ducks with same parameters --- .../Visual/Menus/TestSceneAudioDucking.cs | 39 +++++++++++++++++++ osu.Game/Overlays/MusicController.cs | 3 -- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs index a7ec7f5bdc..8d20d8e0d5 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs @@ -56,6 +56,45 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01)); } + [Test] + public void TestMultipleDucksSameParameters() + { + var duckParameters = new DuckParameters + { + DuckVolumeTo = 0.5, + }; + + IDisposable duckOp1 = null!; + IDisposable duckOp2 = null!; + + double normalVolume = 1; + + AddStep("get initial volume", () => + { + normalVolume = Game.Audio.Tracks.AggregateVolume.Value; + }); + + AddStep("duck one", () => + { + duckOp1 = Game.MusicController.Duck(duckParameters); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); + + AddStep("duck two", () => + { + duckOp2 = Game.MusicController.Duck(duckParameters); + }); + + AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); + + AddStep("restore two", () => duckOp2.Dispose()); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01)); + + AddStep("restore one", () => duckOp1.Dispose()); + AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01)); + } + [Test] public void TestMultipleDucksReverseOrder() { diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index a01f3bfa5f..116e60a014 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -268,9 +268,6 @@ namespace osu.Game.Overlays { parameters ??= new DuckParameters(); - if (duckOperations.Contains(parameters)) - throw new ArgumentException("Ducking has already been applied for the provided parameters.", nameof(parameters)); - duckOperations.Add(parameters); DuckParameters volumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo)!; From 0c5a1410d3395cb712c3f1b05c2f42296c6d7bdb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 07:51:17 +0300 Subject: [PATCH 094/670] Add fade transition to content during open/close --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index f214bcb3a1..1553f37878 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; 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.Effects; @@ -18,6 +19,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Mods { @@ -75,6 +77,7 @@ namespace osu.Game.Overlays.Mods Offset = new Vector2(0f, 5f), Radius = 20f, Roundness = 5f, + Colour = Color4.Black.Opacity(0.25f), }, Expanded = { BindTarget = Expanded }, Children = new Drawable[] @@ -161,13 +164,13 @@ namespace osu.Game.Overlays.Mods content.AutoSizeDuration = 400; content.AutoSizeEasing = Easing.OutQuint; content.AutoSizeAxes = Axes.Y; - content.FadeEdgeEffectTo(0.25f, 120, Easing.OutQuint); + content.FadeIn(120, Easing.OutQuint); } else { content.AutoSizeAxes = Axes.None; content.ResizeHeightTo(header_height, 400, Easing.OutQuint); - content.FadeEdgeEffectTo(0f, 400, Easing.OutQuint); + content.FadeOut(400, Easing.OutSine); } } From a0ffe9bcbc014940f579ef4ff81aa24b5eb57878 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 07:58:40 +0300 Subject: [PATCH 095/670] Remove unnecessary `HandlePositionalInput` override --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 2887b53548..42bc9f16b1 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -18,8 +18,6 @@ namespace osu.Game.Overlays.Mods { public partial class ModCustomisationHeader : OsuHoverContainer { - public override bool HandlePositionalInput => true; - private Box background = null!; private SpriteIcon icon = null!; From 9375f79879fe6ccd6e1d1dacc87dba63d33cee9f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 8 Jul 2024 13:58:42 +0900 Subject: [PATCH 096/670] Add frenzibyte to users that can use diffcalc workflow --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 7fd0f798cd..9f129a697c 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -111,7 +111,7 @@ jobs: steps: - name: Check permissions run: | - ALLOWED_USERS=(smoogipoo peppy bdach) + ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte) for i in "${ALLOWED_USERS[@]}"; do if [[ "${{ github.actor }}" == "$i" ]]; then exit 0 From 22f2f8369521abf943db38387ed173d884f6b0ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:07:45 +0300 Subject: [PATCH 097/670] Rescale chevron up and down instead --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 42bc9f16b1..bf10e13515 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Mods Expanded.BindValueChanged(v => { - icon.RotateTo(v.NewValue ? 180 : 0); + icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); } } From 5223e0aeaef1ac2d05fbca542f5efa5527d9fb39 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:31:33 +0300 Subject: [PATCH 098/670] Use scale instead of rotation for overlay scroll button --- osu.Game/Overlays/OverlayScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index 9ff0a65652..a99cf08abb 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -163,7 +163,7 @@ namespace osu.Game.Overlays LastScrollTarget.BindValueChanged(target => { - spriteIcon.RotateTo(target.NewValue != null ? 180 : 0, fade_duration, Easing.OutQuint); + spriteIcon.ScaleTo(target.NewValue != null ? new Vector2(1f, -1f) : Vector2.One, fade_duration, Easing.OutQuint); TooltipText = target.NewValue != null ? CommonStrings.ButtonsBackToPrevious : CommonStrings.ButtonsBackToTop; }, true); } From be039b85adef209ec7020b1e7c767fce10f55374 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:32:29 +0300 Subject: [PATCH 099/670] Use scale instead of rotation in profile cover toggle button --- .../Overlays/Profile/Header/Components/ToggleCoverButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs index 9171d5de7d..b2d024c1d7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ToggleCoverButton.cs @@ -50,12 +50,13 @@ namespace osu.Game.Overlays.Profile.Header.Components { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(10.5f, 12) + Size = new Vector2(10.5f, 12), + Icon = FontAwesome.Solid.ChevronDown, }; CoverExpanded.BindValueChanged(visible => updateState(visible.NewValue), true); } - private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + private void updateState(bool detailsVisible) => icon.ScaleTo(detailsVisible ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); } } From dc630ddc9d7ebcc9c338894a42579dff0321cbd8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:45:44 +0300 Subject: [PATCH 100/670] Use scale instead of rotation in news month sidebar --- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index 9a748b2001..26490c36c8 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.News.Sidebar Expanded.BindValueChanged(open => { - icon.Scale = new Vector2(1, open.NewValue ? -1 : 1); + icon.ScaleTo(open.NewValue ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); }, true); } } From 4c2ae07eba8f66aec496ddec1a9c148f90bb124b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:48:07 +0300 Subject: [PATCH 101/670] Use scale instead of rotation in `ChevronButton` (used in top-right of comments section) --- osu.Game/Overlays/Comments/Buttons/ChevronButton.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs b/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs index 45024f25db..3902f89688 100644 --- a/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ChevronButton.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Comments.Buttons Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(12), + Icon = FontAwesome.Solid.ChevronDown }; } @@ -38,11 +39,12 @@ namespace osu.Game.Overlays.Comments.Buttons base.LoadComplete(); Action = Expanded.Toggle; Expanded.BindValueChanged(onExpandedChanged, true); + FinishTransforms(true); } private void onExpandedChanged(ValueChangedEvent expanded) { - icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + icon.ScaleTo(expanded.NewValue ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); } } } From 58e236a2471f2f815f947fe5141d3feaa5046de8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:48:52 +0300 Subject: [PATCH 102/670] Add transition to dropdown chevrons --- osu.Game/Collections/CollectionDropdown.cs | 2 +- .../Graphics/UserInterface/OsuDropdown.cs | 25 ++++++++++++++++--- .../Music/NowPlayingCollectionDropdown.cs | 4 +-- .../Overlays/Rankings/SpotlightSelector.cs | 2 +- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index c04689b097..6d8f65c257 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -163,7 +163,7 @@ namespace osu.Game.Collections public CollectionDropdownHeader() { Height = 25; - Icon.Size = new Vector2(16); + Chevron.Size = new Vector2(16); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index c8bb45b59d..562258f5b2 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -30,6 +30,12 @@ namespace osu.Game.Graphics.UserInterface protected override DropdownMenu CreateMenu() => new OsuDropdownMenu(); + public OsuDropdown() + { + if (Header is OsuDropdownHeader osuHeader) + osuHeader.Dropdown = this; + } + public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) return false; @@ -307,7 +313,9 @@ namespace osu.Game.Graphics.UserInterface set => Text.Text = value; } - protected readonly SpriteIcon Icon; + protected readonly SpriteIcon Chevron; + + public OsuDropdown? Dropdown { get; set; } public OsuDropdownHeader() { @@ -341,7 +349,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, }, - Icon = new SpriteIcon + Chevron = new SpriteIcon { Icon = FontAwesome.Solid.ChevronDown, Anchor = Anchor.CentreRight, @@ -365,6 +373,9 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); + if (Dropdown != null) + Dropdown.Menu.StateChanged += _ => updateChevron(); + SearchBar.State.ValueChanged += _ => updateColour(); Enabled.BindValueChanged(_ => updateColour()); updateColour(); @@ -392,16 +403,22 @@ namespace osu.Game.Graphics.UserInterface if (SearchBar.State.Value == Visibility.Visible) { - Icon.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White; + Chevron.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White; Background.Colour = unhoveredColour; } else { - Icon.Colour = Color4.White; + Chevron.Colour = Color4.White; Background.Colour = hovered ? hoveredColour : unhoveredColour; } } + private void updateChevron() + { + bool open = Dropdown?.Menu.State == MenuState.Open; + Chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); + } + protected override DropdownSearchBar CreateSearchBar() => new OsuDropdownSearchBar { Padding = new MarginPadding { Right = 26 }, diff --git a/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs index fa9a2e3972..0f2e9400d9 100644 --- a/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs +++ b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs @@ -53,8 +53,8 @@ namespace osu.Game.Overlays.Music { CornerRadius = 5; Height = 30; - Icon.Size = new Vector2(14); - Icon.Margin = new MarginPadding(0); + Chevron.Size = new Vector2(14); + Chevron.Margin = new MarginPadding(0); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 }; EdgeEffect = new EdgeEffectParameters { diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 190da04a5d..69dc8aba85 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -200,7 +200,7 @@ namespace osu.Game.Overlays.Rankings Text.Font = OsuFont.GetFont(size: 15); Text.Padding = new MarginPadding { Vertical = 1.5f }; // osu-web line-height difference compensation Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 }; - Margin = Icon.Margin = new MarginPadding(0); + Margin = Chevron.Margin = new MarginPadding(0); } [BackgroundDependencyLoader] From 7dc901df11ec893cb45f19ad1a006d1a89cbc0d3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 08:54:05 +0300 Subject: [PATCH 103/670] Do not be lenient on nullability of dropdown --- osu.Game/Graphics/UserInterface/OsuDropdown.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 562258f5b2..71ae149cf6 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.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.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -415,7 +416,8 @@ namespace osu.Game.Graphics.UserInterface private void updateChevron() { - bool open = Dropdown?.Menu.State == MenuState.Open; + Debug.Assert(Dropdown != null); + bool open = Dropdown.Menu.State == MenuState.Open; Chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); } From 95321469783c2e359c4d8efa8cead7b2e94c6b46 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 7 Jul 2024 23:12:20 -0700 Subject: [PATCH 104/670] Fix content overflowing to border The +2 for the top isn't really needed for the original purpose as content fades out now, but visually, having the header and content spacing looks more correct. --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 1553f37878..a1e64e8c49 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Mods { private const float header_height = 42f; private const float content_vertical_padding = 20f; + private const float content_border_thickness = 2f; private Container content = null!; private OsuScrollContainer scrollContainer = null!; @@ -68,7 +69,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.X, BorderColour = colourProvider.Dark3, - BorderThickness = 2f, + BorderThickness = content_border_thickness, CornerRadius = 10f, Masking = true, EdgeEffect = new EdgeEffectParameters @@ -90,9 +91,11 @@ namespace osu.Game.Overlays.Mods scrollContainer = new OsuScrollContainer(Direction.Vertical) { RelativeSizeAxes = Axes.X, - // The +2f is a workaround for masking issues (see https://github.com/ppy/osu-framework/issues/1675#issuecomment-910023157) - // Note that this actually causes the full scroll range to be reduced by 2px at the bottom, but it's not really noticeable. - Margin = new MarginPadding { Top = header_height + 2f }, + Margin = new MarginPadding + { + Top = header_height + content_border_thickness, + Bottom = content_border_thickness + }, Child = sectionsFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, From 03bd6069d88827575f19018c0d599cc3e865972d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 15:50:27 +0900 Subject: [PATCH 105/670] Add slight animation when revert to default button is displayed This also fixes the transforms running too often (could make the initial transform take longer than expected if adjusting a slider bar, for instance). --- osu.Game/Overlays/RevertToDefaultButton.cs | 28 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/RevertToDefaultButton.cs b/osu.Game/Overlays/RevertToDefaultButton.cs index 6fa5209f64..1ebe7b7934 100644 --- a/osu.Game/Overlays/RevertToDefaultButton.cs +++ b/osu.Game/Overlays/RevertToDefaultButton.cs @@ -87,6 +87,7 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); + updateState(); FinishTransforms(true); } @@ -95,33 +96,50 @@ namespace osu.Game.Overlays protected override bool OnHover(HoverEvent e) { - UpdateState(); + updateHover(); return false; } protected override void OnHoverLost(HoverLostEvent e) { - UpdateState(); + updateHover(); } public void UpdateState() => Scheduler.AddOnce(updateState); private const double fade_duration = 200; + private bool? isDisplayed; + private void updateState() { if (current == null) return; - Enabled.Value = !current.Disabled; + // Avoid running animations if we are already in an up-to-date state. + if (Enabled.Value == !current.Disabled && isDisplayed == !current.IsDefault) + return; - if (current.IsDefault) + Enabled.Value = !current.Disabled; + isDisplayed = !current.IsDefault; + + updateHover(); + + if (isDisplayed == false) this.FadeTo(0, fade_duration, Easing.OutQuint); else if (current.Disabled) this.FadeTo(0.2f, fade_duration, Easing.OutQuint); else - this.FadeTo(1, fade_duration, Easing.OutQuint); + { + icon.RotateTo(150).RotateTo(0, fade_duration * 2, Easing.OutQuint); + icon.ScaleTo(0.7f).ScaleTo(1, fade_duration * 2, Easing.OutQuint); + this.FadeTo(1, fade_duration, Easing.OutQuint); + } + } + + private void updateHover() + { if (IsHovered && Enabled.Value) { icon.RotateTo(-40, 500, Easing.OutQuint); From 73d164e0d0d7861d6d81c361e271ea09116ba9a2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jul 2024 00:30:16 -0700 Subject: [PATCH 106/670] Add chevron transition to `CommentRepliesButton` --- osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 400820ddd9..543ed7e722 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Comments.Buttons background.Colour = colourProvider.Background2; } - protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); + protected void SetIconDirection(bool upwards) => icon.ScaleTo(upwards ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); public void ToggleTextVisibility(bool visible) => text.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); From 257de9d08b01dbd56e3caa9a158d78c896c7cae8 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jul 2024 01:00:36 -0700 Subject: [PATCH 107/670] Fix arrow direction test --- .../Visual/UserInterface/TestSceneCommentRepliesButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index eaaf40fb36..0aef56bc2e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestArrowDirection() { AddStep("Set upwards", () => button.SetIconDirection(true)); - AddAssert("Icon facing upwards", () => button.Icon.Scale.Y == -1); + AddUntilStep("Icon facing upwards", () => button.Icon.Scale.Y == -1); AddStep("Set downwards", () => button.SetIconDirection(false)); - AddAssert("Icon facing downwards", () => button.Icon.Scale.Y == 1); + AddUntilStep("Icon facing downwards", () => button.Icon.Scale.Y == 1); } private partial class TestButton : CommentRepliesButton From 2ceedb0f93cfa2e263372113668c0d307cc34dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 11:04:15 +0200 Subject: [PATCH 108/670] Fix editor menus overflowing off screen Closes https://github.com/ppy/osu/issues/28750. Yes this is not the perfect change to fix this (which would probably be some framework change to take bounds of the parenting input manager into account). I really do not want to go there and would like to just fix this locally and move on. Due to the game-wide scaling container this sorta works for any resolution anyhow. --- .../Screens/Edit/Components/Menus/EditorMenuBar.cs | 10 ++++++++-- osu.Game/Screens/Edit/Editor.cs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 0e125d0ec0..ee954a7ea0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -71,7 +71,10 @@ namespace osu.Game.Screens.Edit.Components.Menus }); } - protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu + { + MaxHeight = MaxHeight, + }; protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item); @@ -143,7 +146,10 @@ namespace osu.Game.Screens.Edit.Components.Menus BackgroundColour = colourProvider.Background2; } - protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); + protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu + { + MaxHeight = MaxHeight, + }; protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c00b7ac4f2..2278af040f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -343,6 +343,7 @@ namespace osu.Game.Screens.Edit Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, + MaxHeight = 600, Items = new[] { new MenuItem(CommonStrings.MenuBarFile) From db7774485a94c6c38914093d2a3fde7318c406f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 11:14:32 +0200 Subject: [PATCH 109/670] Fix editor player crashing with UR counter present in skin Closes https://github.com/ppy/osu/issues/28764. --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 616d7a09b2..4377cc6219 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -72,7 +72,11 @@ namespace osu.Game.Screens.Edit.GameplayTest foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time)) { var judgement = hitObject.Judgement; - var result = new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult }; + var result = new JudgementResult(hitObject, judgement) + { + Type = judgement.MaxResult, + GameplayRate = GameplayClockContainer.GetTrueGameplayRate(), + }; HealthProcessor.ApplyResult(result); ScoreProcessor.ApplyResult(result); From 910153c2e06c267819f78d225baae7720c0e17f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 19:32:28 +0900 Subject: [PATCH 110/670] Fix sizing / padding of collection dropdown header --- osu.Game/Collections/CollectionDropdown.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 6d8f65c257..1e47aff3ec 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -163,8 +163,8 @@ namespace osu.Game.Collections public CollectionDropdownHeader() { Height = 25; - Chevron.Size = new Vector2(16); - Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; + Chevron.Size = new Vector2(12); + Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 8 }; } } From e51d510ea3efea73cbe05620b5d97d87b6beed29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 20:05:07 +0900 Subject: [PATCH 111/670] Add failing test for beatmap set hard deletion --- .../Visual/SongSelect/TestScenePlaySongSelect.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 6581ce0323..4c6a5c93d9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1211,6 +1211,20 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); } + [Test] + [Solo] + public void TestHardDeleteHandledCorrectly() + { + createSongSelect(); + + addRulesetImportStep(0); + AddAssert("3 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "3 matches"); + + AddStep("hard delete beatmap", () => Realm.Write(r => r.RemoveRange(r.All().Where(s => !s.Protected)))); + + AddUntilStep("0 matching shown", () => songSelect.ChildrenOfType().Single().InformationalText == "0 matches"); + } + [Test] public void TestDeleteHotkey() { From 151c44853558b15440c11dd4dc7f74737eb1ff2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 18:31:36 +0900 Subject: [PATCH 112/670] Simplify tracking of beatmap sets in `BeatmapCarousel` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 73 ++++++---------------- 1 file changed, 19 insertions(+), 54 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 56e7c24985..cab258aaab 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -197,7 +197,6 @@ namespace osu.Game.Screens.Select private CarouselRoot root; private IDisposable? subscriptionSets; - private IDisposable? subscriptionDeletedSets; private IDisposable? subscriptionBeatmaps; private IDisposable? subscriptionHiddenBeatmaps; @@ -253,6 +252,11 @@ namespace osu.Game.Screens.Select [Resolved] private RealmAccess realm { get; set; } = null!; + /// + /// Track GUIDs of all sets in realm to allow handling deletions. + /// + private readonly List realmBeatmapSets = new List(); + protected override void LoadComplete() { base.LoadComplete(); @@ -262,45 +266,9 @@ namespace osu.Game.Screens.Select // Can't use main subscriptions because we can't lookup deleted indices. // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionDeletedSets = realm.RegisterForNotifications(r => r.All().Where(s => s.DeletePending && !s.Protected), deletedBeatmapSetsChanged); subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } - private void deletedBeatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) - { - // If loading test beatmaps, avoid overwriting with realm subscription callbacks. - if (loadedTestBeatmaps) - return; - - if (changes == null) - return; - - var removeableSets = changes.InsertedIndices.Select(i => sender[i].ID).ToHashSet(); - - // This schedule is required to retain selection of beatmaps over an ImportAsUpdate operation. - // This is covered by TestPlaySongSelect.TestSelectionRetainedOnBeatmapUpdate. - // - // In short, we have specialised logic in `beatmapSetsChanged` (directly below) to infer that an - // update operation has occurred. For this to work, we need to confirm the `DeletePending` flag - // of the current selection. - // - // If we don't schedule the following code, it is possible for the `deleteBeatmapSetsChanged` handler - // to be invoked before the `beatmapSetsChanged` handler (realm call order seems non-deterministic) - // which will lead to the currently selected beatmap changing via `CarouselGroupEagerSelect`. - // - // We need a better path forward here. A few ideas: - // - Avoid the necessity of having realm subscriptions on deleted/hidden items, maybe by storing all guids in realm - // to a local list so we can better look them up on receiving `DeletedIndices`. - // - Add a new property on `BeatmapSetInfo` to link to the pre-update set, and use that to handle the update case. - Schedule(() => - { - foreach (var set in removeableSets) - removeBeatmapSet(set); - - invalidateAfterChange(); - }); - } - private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. @@ -312,32 +280,30 @@ namespace osu.Game.Screens.Select if (changes == null) { - // During initial population, we must manually account for the fact that our original query was done on an async thread. - // Since then, there may have been imports or deletions. - // Here we manually catch up on any changes. - var realmSets = new HashSet(); + realmBeatmapSets.Clear(); + realmBeatmapSets.AddRange(sender.Select(r => r.ID)); - for (int i = 0; i < sender.Count; i++) - realmSets.Add(sender[i].ID); - - foreach (var id in realmSets) + foreach (var id in realmBeatmapSets) { if (!root.BeatmapSetsByID.ContainsKey(id)) setsRequiringUpdate.Add(realm.Realm.Find(id)!.Detach()); } - - foreach (var id in root.BeatmapSetsByID.Keys) - { - if (!realmSets.Contains(id)) - setsRequiringRemoval.Add(id); - } } else { - foreach (int i in changes.NewModifiedIndices) - setsRequiringUpdate.Add(sender[i].Detach()); + foreach (int i in changes.DeletedIndices.OrderDescending()) + { + setsRequiringRemoval.Add(realmBeatmapSets[i]); + realmBeatmapSets.RemoveAt(i); + } foreach (int i in changes.InsertedIndices) + { + realmBeatmapSets.Insert(i, sender[i].ID); + setsRequiringUpdate.Add(sender[i].Detach()); + } + + foreach (int i in changes.NewModifiedIndices) setsRequiringUpdate.Add(sender[i].Detach()); } @@ -1312,7 +1278,6 @@ namespace osu.Game.Screens.Select base.Dispose(isDisposing); subscriptionSets?.Dispose(); - subscriptionDeletedSets?.Dispose(); subscriptionBeatmaps?.Dispose(); subscriptionHiddenBeatmaps?.Dispose(); } From 1095137a5b82d7c7978d8d80ea6f379a5edf5b70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 18:48:53 +0900 Subject: [PATCH 113/670] Simplify tracking of hidden beatmaps Handling a few extra events is preferrable to keeping a second realm subscription live. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cab258aaab..b7c9c16112 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -198,7 +198,6 @@ namespace osu.Game.Screens.Select private IDisposable? subscriptionSets; private IDisposable? subscriptionBeatmaps; - private IDisposable? subscriptionHiddenBeatmaps; private readonly DrawablePool setPool = new DrawablePool(100); @@ -263,10 +262,6 @@ namespace osu.Game.Screens.Select subscriptionSets = realm.RegisterForNotifications(getBeatmapSets, beatmapSetsChanged); subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); - - // Can't use main subscriptions because we can't lookup deleted indices. - // https://github.com/realm/realm-dotnet/discussions/2634#discussioncomment-1605595. - subscriptionHiddenBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => b.Hidden), beatmapsChanged); } private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) @@ -1279,7 +1274,6 @@ namespace osu.Game.Screens.Select subscriptionSets?.Dispose(); subscriptionBeatmaps?.Dispose(); - subscriptionHiddenBeatmaps?.Dispose(); } } } From 8f271170e94fa7fdf547b70b5f107a7679194d0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 19:29:03 +0900 Subject: [PATCH 114/670] Coalesce beatmap updates --- osu.Game/Screens/Select/BeatmapCarousel.cs | 103 +++++++++++---------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b7c9c16112..1224bcc6de 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -244,6 +244,9 @@ namespace osu.Game.Screens.Select if (!loadedTestBeatmaps) { + // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons + // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update + // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). realm.Run(r => loadBeatmapSets(getBeatmapSets(r))); } } @@ -264,25 +267,21 @@ namespace osu.Game.Screens.Select subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); } + private readonly HashSet setsRequiringUpdate = new HashSet(); + private readonly HashSet setsRequiringRemoval = new HashSet(); + private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) { // If loading test beatmaps, avoid overwriting with realm subscription callbacks. if (loadedTestBeatmaps) return; - var setsRequiringUpdate = new HashSet(); - var setsRequiringRemoval = new HashSet(); - if (changes == null) { realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); - foreach (var id in realmBeatmapSets) - { - if (!root.BeatmapSetsByID.ContainsKey(id)) - setsRequiringUpdate.Add(realm.Realm.Find(id)!.Detach()); - } + loadBeatmapSets(sender); } else { @@ -302,62 +301,64 @@ namespace osu.Game.Screens.Select setsRequiringUpdate.Add(sender[i].Detach()); } - // All local operations must be scheduled. - // - // If we don't schedule, beatmaps getting changed while song select is suspended (ie. last played being updated) - // will cause unexpected sounds and operations to occur in the background. - Schedule(() => + Scheduler.AddOnce(processBeatmapChanges); + } + + // All local operations must be scheduled. + // + // If we don't schedule, beatmaps getting changed while song select is suspended (ie. last played being updated) + // will cause unexpected sounds and operations to occur in the background. + private void processBeatmapChanges() + { + try { - try + foreach (var set in setsRequiringRemoval) removeBeatmapSet(set); + + foreach (var set in setsRequiringUpdate) updateBeatmapSet(set); + + if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null) { - foreach (var set in setsRequiringRemoval) - removeBeatmapSet(set); + // If SelectedBeatmapInfo is non-null, the set should also be non-null. + Debug.Assert(SelectedBeatmapSet != null); - foreach (var set in setsRequiringUpdate) - updateBeatmapSet(set); + // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. + // When an update occurs, the previous beatmap set is either soft or hard deleted. + // Check if the current selection was potentially deleted by re-querying its validity. + bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID)?.DeletePending != false); - if (changes?.DeletedIndices.Length > 0 && SelectedBeatmapInfo != null) + if (selectedSetMarkedDeleted && setsRequiringUpdate.Any()) { - // If SelectedBeatmapInfo is non-null, the set should also be non-null. - Debug.Assert(SelectedBeatmapSet != null); - - // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. - // When an update occurs, the previous beatmap set is either soft or hard deleted. - // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID)?.DeletePending != false); - - if (selectedSetMarkedDeleted && setsRequiringUpdate.Any()) + // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. + // This relies on the full update operation being in a single transaction, so please don't change that. + foreach (var set in setsRequiringUpdate) { - // If it is no longer valid, make the bold assumption that an updated version will be available in the modified/inserted indices. - // This relies on the full update operation being in a single transaction, so please don't change that. - foreach (var set in setsRequiringUpdate) + foreach (var beatmapInfo in set.Beatmaps) { - foreach (var beatmapInfo in set.Beatmaps) - { - if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) - continue; + if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue; - // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. - if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName) - { - SelectBeatmap(beatmapInfo); - return; - } + // Best effort matching. We can't use ID because in the update flow a new version will get its own GUID. + if (beatmapInfo.DifficultyName == SelectedBeatmapInfo.DifficultyName) + { + SelectBeatmap(beatmapInfo); + return; } } - - // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. - // Let's attempt to follow set-level selection anyway. - SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First()); } + + // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. + // Let's attempt to follow set-level selection anyway. + SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First()); } } - finally - { - BeatmapSetsLoaded = true; - invalidateAfterChange(); - } - }); + } + finally + { + BeatmapSetsLoaded = true; + invalidateAfterChange(); + } + + setsRequiringRemoval.Clear(); + setsRequiringUpdate.Clear(); } private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) From 6433f29651855fe8a5379d8b8e8f9635b256a568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 13:15:24 +0200 Subject: [PATCH 115/670] Add failing test case --- .../Visual/Editing/TestSceneEditorClock.cs | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index ed58c59ff0..54cc86bc6b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; @@ -19,9 +22,10 @@ namespace osu.Game.Tests.Visual.Editing [Cached] private EditorBeatmap editorBeatmap = new EditorBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)); - public TestSceneEditorClock() + [SetUpSteps] + public void SetUpSteps() { - Add(new FillFlowContainer + AddStep("create content", () => Add(new FillFlowContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -39,19 +43,17 @@ namespace osu.Game.Tests.Visual.Editing Size = new Vector2(200, 100) } } + })); + AddStep("set working beatmap", () => + { + Beatmap.Disabled = false; + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + // ensure that music controller does not change this beatmap due to it + // completing naturally as part of the test. + Beatmap.Disabled = true; }); } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - // ensure that music controller does not change this beatmap due to it - // completing naturally as part of the test. - Beatmap.Disabled = true; - } - [Test] public void TestStopAtTrackEnd() { @@ -102,6 +104,18 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); } + [Test] + public void TestAdjustmentsRemovedOnDisposal() + { + AddStep("reset clock", () => EditorClock.Seek(0)); + + AddStep("set 0.25x speed", () => this.ChildrenOfType>().First().Current.Value = 0.25); + AddAssert("track has 0.25x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(0.25)); + + AddStep("dispose playback control", () => Clear(disposeChildren: true)); + AddAssert("track has 1x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(1)); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; From 0fe2c45e1d61fd18aadcfa52762a4600b9f952ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 13:15:50 +0200 Subject: [PATCH 116/670] Fix editor playback control not removing correct adjustment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes https://github.com/ppy/osu/issues/28768. great job past me 🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦🤦 --- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 0546878788..6319dc892e 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit.Components protected override void Dispose(bool isDisposing) { - Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, tempoAdjustment); + Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempoAdjustment); base.Dispose(isDisposing); } From 2822ba23770e42d34f46425caeef6c2a061467a1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 8 Jul 2024 13:30:11 +0200 Subject: [PATCH 117/670] Fix CurrentTimeAccurate being inaccurate if seeking smoothly in the same frame and a transform is already active --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 11 +++++++++++ osu.Game/Screens/Edit/EditorClock.cs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index ed58c59ff0..b38c9abfb6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -102,6 +102,17 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength)); } + [Test] + public void TestCurrentTimeDoubleTransform() + { + AddAssert("seek smoothly twice and current time is accurate", () => + { + EditorClock.SeekSmoothlyTo(1000); + EditorClock.SeekSmoothlyTo(2000); + return 2000 == EditorClock.CurrentTimeAccurate; + }); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index d5ca6fc35e..773abaa737 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Edit /// The current time of this clock, include any active transform seeks performed via . /// public double CurrentTimeAccurate => - Transforms.OfType().FirstOrDefault()?.EndValue ?? CurrentTime; + Transforms.OfType().LastOrDefault()?.EndValue ?? CurrentTime; public double CurrentTime => underlyingClock.CurrentTime; From d5158d10356ee9ef11b6eeffeb0b75f5ad3df4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 13:36:30 +0200 Subject: [PATCH 118/670] Fix incorrect changes around success callback refactor --- .../OnlinePlay/Playlists/PlaylistItemResultsScreen.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs index 51fd912ccc..dc06b88823 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs @@ -113,8 +113,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists setPositions(lowerScores, userScore.Position.Value, 1); } - Schedule(() => PerformSuccessCallback(scoresCallback, allScores)); - hideLoadingSpinners(); + Schedule(() => + { + PerformSuccessCallback(scoresCallback, allScores); + hideLoadingSpinners(); + }); }; // On failure, fallback to a normal index. @@ -169,7 +172,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Schedule(() => { PerformSuccessCallback(scoresCallback, r.Scores, r); - hideLoadingSpinners(pivot); + hideLoadingSpinners(r); }); }; From da4067d059b504261002d943afef5c7fc78d661f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 16:06:31 +0300 Subject: [PATCH 119/670] Add failing test case --- .../Gameplay/TestSceneUnorderedBreaks.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneUnorderedBreaks.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnorderedBreaks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnorderedBreaks.cs new file mode 100644 index 0000000000..04265ccc03 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnorderedBreaks.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Play; +using osu.Game.Storyboards; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneUnorderedBreaks : OsuPlayerTestScene + { + [Resolved] + private AudioManager audioManager { get; set; } = null!; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new OsuBeatmap(); + beatmap.HitObjects.Add(new HitCircle { StartTime = 0 }); + beatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); + beatmap.HitObjects.Add(new HitCircle { StartTime = 10000 }); + beatmap.Breaks.Add(new BreakPeriod(6000, 9000)); + beatmap.Breaks.Add(new BreakPeriod(1000, 4000)); + return beatmap; + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + } + + [Test] + public void TestBreakOverlayVisibility() + { + AddAssert("break overlay hidden", () => !this.ChildrenOfType().Single().Child.IsPresent); + addSeekStep(2000); + AddUntilStep("break overlay visible", () => this.ChildrenOfType().Single().Child.IsPresent); + addSeekStep(5000); + AddAssert("break overlay hidden", () => !this.ChildrenOfType().Single().Child.IsPresent); + addSeekStep(7000); + AddUntilStep("break overlay visible", () => this.ChildrenOfType().Single().Child.IsPresent); + addSeekStep(10000); + AddAssert("break overlay hidden", () => !this.ChildrenOfType().Single().Child.IsPresent); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time)); + + // Allow a few frames of lenience + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + } +} From b33e54d0647916c5f37743f6bcf4b115c04aa62d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 8 Jul 2024 12:05:15 +0300 Subject: [PATCH 120/670] Enforce `IBeatmap.Breaks` to be sorted chronologically --- osu.Game/Beatmaps/Beatmap.cs | 3 ++- osu.Game/Beatmaps/BeatmapConverter.cs | 5 ++++- osu.Game/Beatmaps/IBeatmap.cs | 3 ++- osu.Game/Beatmaps/Timing/BreakPeriod.cs | 14 +++++++++++++- .../Rulesets/Difficulty/DifficultyCalculator.cs | 3 ++- osu.Game/Screens/Edit/EditorBeatmap.cs | 9 +++++++-- 6 files changed, 30 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ae77e4adcf..d12ff59198 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using Newtonsoft.Json; +using osu.Framework.Lists; using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps @@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); - public List Breaks { get; set; } = new List(); + public SortedList Breaks { get; set; } = new SortedList(); public List UnhandledEventLines { get; set; } = new List(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 676eb1b159..c43bd494e9 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using osu.Framework.Lists; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; @@ -50,7 +52,8 @@ namespace osu.Game.Beatmaps original.ControlPointInfo = original.ControlPointInfo.DeepClone(); // Used in osu!mania conversion. - original.Breaks = original.Breaks.ToList(); + original.Breaks = new SortedList(Comparer.Default); + original.Breaks.AddRange(Beatmap.Breaks); return ConvertBeatmap(original, cancellationToken); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 176738489a..430a31769b 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Lists; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; @@ -40,7 +41,7 @@ namespace osu.Game.Beatmaps /// /// The breaks in this beatmap. /// - List Breaks { get; set; } + SortedList Breaks { get; set; } /// /// All lines from the [Events] section which aren't handled in the encoding process yet. diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index d8b500227a..921cfe9c51 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -6,7 +6,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Beatmaps.Timing { - public class BreakPeriod : IEquatable + public class BreakPeriod : IEquatable, IComparable { /// /// The minimum gap between the start of the break and the previous object. @@ -76,5 +76,17 @@ namespace osu.Game.Beatmaps.Timing && EndTime == other.EndTime; public override int GetHashCode() => HashCode.Combine(StartTime, EndTime); + + public int CompareTo(BreakPeriod? other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + + int result = StartTime.CompareTo(other.StartTime); + if (result != 0) + return result; + + return EndTime.CompareTo(other.EndTime); + } } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 722263c58e..63b27243d0 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -10,6 +10,7 @@ using System.Threading; using JetBrains.Annotations; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -327,7 +328,7 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.Difficulty = value; } - public List Breaks + public SortedList Breaks { get => baseBeatmap.Breaks; set => baseBeatmap.Breaks = value; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index c8592b5bea..ad31c2ccc3 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,6 +10,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Legacy; @@ -111,7 +112,11 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); Breaks = new BindableList(playableBeatmap.Breaks); - Breaks.BindCollectionChanged((_, _) => playableBeatmap.Breaks = Breaks.ToList()); + Breaks.BindCollectionChanged((_, _) => + { + playableBeatmap.Breaks.Clear(); + playableBeatmap.Breaks.AddRange(Breaks); + }); PreviewTime = new BindableInt(BeatmapInfo.Metadata.PreviewTime); PreviewTime.BindValueChanged(s => @@ -177,7 +182,7 @@ namespace osu.Game.Screens.Edit public readonly BindableList Breaks; - List IBeatmap.Breaks + SortedList IBeatmap.Breaks { get => PlayableBeatmap.Breaks; set => PlayableBeatmap.Breaks = value; From 0d22c9a9c6b2c0deafdf3cae0afeb557d1584a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 15:58:21 +0200 Subject: [PATCH 121/670] Pass comparer in all usages for consistency --- osu.Game/Beatmaps/Beatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index d12ff59198..282f8fe794 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo(); - public SortedList Breaks { get; set; } = new SortedList(); + public SortedList Breaks { get; set; } = new SortedList(Comparer.Default); public List UnhandledEventLines { get; set; } = new List(); From 449d50a46a6641b1920104a383366a3a8ae25983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 16:25:19 +0200 Subject: [PATCH 122/670] Add failing test case --- .../Editor/TestSceneEditor.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs index 762238be47..ef4e09c26b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneEditor.cs @@ -1,13 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Testing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Screens.Edit.Timing; using osu.Game.Tests.Visual; +using osuTK.Input; namespace osu.Game.Rulesets.Mania.Tests.Editor { @@ -30,5 +35,43 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor var config = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull(); config.BindWith(ManiaRulesetSetting.ScrollDirection, direction); } + + [Test] + public void TestReloadOnBPMChange() + { + HitObjectComposer oldComposer = null!; + + AddStep("store composer", () => oldComposer = this.ChildrenOfType().Single()); + AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null); + AddStep("switch to timing tab", () => InputManager.Key(Key.F3)); + AddUntilStep("wait for loaded", () => this.ChildrenOfType().ElementAtOrDefault(1), () => Is.Not.Null); + AddStep("change timing point BPM", () => + { + var bpmControl = this.ChildrenOfType().ElementAt(1); + InputManager.MoveMouseTo(bpmControl); + InputManager.Click(MouseButton.Left); + }); + + AddStep("switch back to composer", () => InputManager.Key(Key.F1)); + AddUntilStep("composer reloaded", () => + { + var composer = this.ChildrenOfType().SingleOrDefault(); + return composer != null && composer != oldComposer; + }); + + AddStep("store composer", () => oldComposer = this.ChildrenOfType().Single()); + AddUntilStep("composer stored", () => oldComposer, () => Is.Not.Null); + AddStep("undo", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Z); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddUntilStep("composer reloaded", () => + { + var composer = this.ChildrenOfType().SingleOrDefault(); + return composer != null && composer != oldComposer; + }); + } } } From 275b959c02801f43563afa471f2ccd0bdd02b9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 16:28:52 +0200 Subject: [PATCH 123/670] Fix composer disappearing when undoing change to control points As mentioned in https://github.com/ppy/osu/issues/28752. Regressed in https://github.com/ppy/osu/pull/28444. --- osu.Game/Screens/Edit/Editor.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2278af040f..a8a28ef0b8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1023,11 +1023,15 @@ namespace osu.Game.Screens.Edit /// /// Forces a reload of the compose screen after significant configuration changes. /// - /// - /// This can be necessary for scrolling rulesets, as they do not easily support control points changing under them. - /// The reason that this works is that will re-instantiate the screen whenever it is requested next. - /// - public void ReloadComposeScreen() => screenContainer.SingleOrDefault(s => s.Type == EditorScreenMode.Compose)?.RemoveAndDisposeImmediately(); + public void ReloadComposeScreen() + { + screenContainer.SingleOrDefault(s => s.Type == EditorScreenMode.Compose)?.RemoveAndDisposeImmediately(); + + // If not currently on compose screen, the reload will happen on next mode change. + // That said, control points *can* change on compose screen (e.g. via undo), so we have to handle that case too. + if (Mode.Value == EditorScreenMode.Compose) + Mode.TriggerChange(); + } [CanBeNull] private ScheduledDelegate playbackDisabledDebounce; From 9a61adc4bc02021f7321d942fc0c7a000b61a666 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jul 2024 23:59:20 +0900 Subject: [PATCH 124/670] Ensure other lists are cleared when realm is reset --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 1224bcc6de..9d61e2a977 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -280,6 +280,8 @@ namespace osu.Game.Screens.Select { realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + setsRequiringRemoval.Clear(); + setsRequiringUpdate.Clear(); loadBeatmapSets(sender); } From ae78c13684b74d6433492e792edd38600e48bfa8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 5 Mar 2024 20:44:32 +0800 Subject: [PATCH 125/670] Use Environment.IsPrivilegedProcess --- .../Security/ElevatedPrivilegesChecker.cs | 37 +------------------ osu.Desktop/osu.Desktop.csproj | 1 - 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs index 6665733656..0bed9830df 100644 --- a/osu.Desktop/Security/ElevatedPrivilegesChecker.cs +++ b/osu.Desktop/Security/ElevatedPrivilegesChecker.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Security.Principal; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -21,48 +20,14 @@ namespace osu.Desktop.Security [Resolved] private INotificationOverlay notifications { get; set; } = null!; - private bool elevated; - - [BackgroundDependencyLoader] - private void load() - { - elevated = checkElevated(); - } - protected override void LoadComplete() { base.LoadComplete(); - if (elevated) + if (Environment.IsPrivilegedProcess) notifications.Post(new ElevatedPrivilegesNotification()); } - private bool checkElevated() - { - try - { - switch (RuntimeInfo.OS) - { - case RuntimeInfo.Platform.Windows: - if (!OperatingSystem.IsWindows()) return false; - - var windowsIdentity = WindowsIdentity.GetCurrent(); - var windowsPrincipal = new WindowsPrincipal(windowsIdentity); - - return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); - - case RuntimeInfo.Platform.macOS: - case RuntimeInfo.Platform.Linux: - return Mono.Unix.Native.Syscall.geteuid() == 0; - } - } - catch - { - } - - return false; - } - private partial class ElevatedPrivilegesNotification : SimpleNotification { public override bool IsImportant => true; diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index e7a63bd921..acb53835a3 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,6 @@ - From cd968d4185bb34dbe177673c5960eb68714b2ac5 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 8 Jul 2024 16:46:35 -0700 Subject: [PATCH 126/670] Add caret transition to beatmap sort tab items --- .../Overlays/BeatmapListing/BeatmapListingSortTabControl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs index e3e2bcaf9a..7f8b68fd6c 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSortTabControl.cs @@ -126,7 +126,8 @@ namespace osu.Game.Overlays.BeatmapListing Origin = Anchor.Centre, AlwaysPresent = true, Alpha = 0, - Size = new Vector2(6) + Size = new Vector2(6), + Icon = FontAwesome.Solid.CaretDown, }); } @@ -136,7 +137,7 @@ namespace osu.Game.Overlays.BeatmapListing SortDirection.BindValueChanged(direction => { - icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending && Active.Value ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown; + icon.ScaleTo(direction.NewValue == Overlays.SortDirection.Ascending && Active.Value ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint); }, true); } From c8b9c117cded884aaa4d7d97e5116300f07b81b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 15:47:41 +0900 Subject: [PATCH 127/670] Add failing test showing realm not sending through null `ChangeSet` --- .../RealmSubscriptionRegistrationTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 45842a952a..14864f7aa1 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -71,6 +71,35 @@ namespace osu.Game.Tests.Database } } + [Test] + public void TestSubscriptionInitialChangeSetNull() + { + ChangeSet? firstChanges = null; + int receivedChangesCount = 0; + + RunTestWithRealm((realm, _) => + { + var registration = realm.RegisterForNotifications(r => r.All(), onChanged); + + realm.WriteAsync(r => r.Add(TestResources.CreateTestBeatmapSetInfo())).WaitSafely(); + + realm.Run(r => r.Refresh()); + + Assert.That(receivedChangesCount, Is.EqualTo(2)); + Assert.That(firstChanges, Is.Null); + + registration.Dispose(); + }); + + void onChanged(IRealmCollection sender, ChangeSet? changes) + { + if (receivedChangesCount == 0) + firstChanges = changes; + + receivedChangesCount++; + } + } + [Test] public void TestSubscriptionWithAsyncWrite() { From 2423bbb776bb0fd5042693684cd2e57ac7f3eff8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 15:28:35 +0900 Subject: [PATCH 128/670] Ensure realm subscriptions always fire initial callback with null `ChangeSet` We expect this to be the case, but it turns out that it [may be coalesced](https://www.mongodb.com/docs/realm-sdks/dotnet/latest/reference/Realms.IRealmCollection-1.html#Realms_IRealmCollection_1_SubscribeForNotifications_Realms_NotificationCallbackDelegate__0__Realms_KeyPathsCollection_): > Notifications are delivered via the standard event loop, and so can't > be delivered while the event loop is blocked by other activity. When > notifications can't be delivered instantly, multiple notifications may > be coalesced into a single notification. This can include the > notification with the initial collection. Rather than struggle with handling this locally every time, let's fix the callback at our end to ensure we receive the initial null case. I've raised concern for the API being a bit silly with realm (https://github.com/realm/realm-dotnet/issues/3641). --- osu.Game/Database/RealmObjectExtensions.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 72529ed9ff..bd8c52bb85 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -65,7 +65,8 @@ namespace osu.Game.Database if (!d.Beatmaps.Contains(existingBeatmap)) { Debug.Fail("Beatmaps should never become detached under normal circumstances. If this ever triggers, it should be investigated further."); - Logger.Log("WARNING: One of the difficulties in a beatmap was detached from its set. Please save a copy of logs and report this to devs.", LoggingTarget.Database, LogLevel.Important); + Logger.Log("WARNING: One of the difficulties in a beatmap was detached from its set. Please save a copy of logs and report this to devs.", LoggingTarget.Database, + LogLevel.Important); d.Beatmaps.Add(existingBeatmap); } @@ -291,7 +292,21 @@ namespace osu.Game.Database if (!RealmAccess.CurrentThreadSubscriptionsAllowed) throw new InvalidOperationException($"Make sure to call {nameof(RealmAccess)}.{nameof(RealmAccess.RegisterForNotifications)}"); - return collection.SubscribeForNotifications(callback); + bool initial = true; + return collection.SubscribeForNotifications(((sender, changes) => + { + if (initial) + { + initial = false; + + // Realm might coalesce the initial callback, meaning we never receive a `ChangeSet` of `null` marking the first callback. + // Let's decouple it for simplicity in handling. + if (changes != null) + callback(sender, null); + } + + callback(sender, changes); + })); } /// From ee9e329db33c11cca18699e5d6396cedff3e07f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 16:05:58 +0900 Subject: [PATCH 129/670] Inhibit original callback from firing when sending initial changeset --- osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs | 2 +- osu.Game/Database/RealmObjectExtensions.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 14864f7aa1..e5be4d665b 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Database realm.Run(r => r.Refresh()); - Assert.That(receivedChangesCount, Is.EqualTo(2)); + Assert.That(receivedChangesCount, Is.EqualTo(1)); Assert.That(firstChanges, Is.Null); registration.Dispose(); diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index bd8c52bb85..2fa3b8a880 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -302,7 +302,10 @@ namespace osu.Game.Database // Realm might coalesce the initial callback, meaning we never receive a `ChangeSet` of `null` marking the first callback. // Let's decouple it for simplicity in handling. if (changes != null) + { callback(sender, null); + return; + } } callback(sender, changes); From aadcc5384d77201aacdac55e93e3f5743e2757bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 16:16:19 +0900 Subject: [PATCH 130/670] Adjust editor transparent tweens to be less "flashy" Touched on in https://github.com/ppy/osu/discussions/28581. After a bit more usage of the editor I do agree with this and think that making the fades a bit more gentle helps a lot. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 ++++++++++--- osu.Game/Screens/Edit/BottomBar.cs | 9 ++++++--- .../Compose/Components/Timeline/TimelineArea.cs | 8 +++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 18a50763db..1ba488d027 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -269,9 +269,16 @@ namespace osu.Game.Rulesets.Edit composerFocusMode.BindValueChanged(_ => { - float targetAlpha = composerFocusMode.Value ? 0.5f : 1; - leftToolboxBackground.FadeTo(targetAlpha, 400, Easing.OutQuint); - rightToolboxBackground.FadeTo(targetAlpha, 400, Easing.OutQuint); + if (!composerFocusMode.Value) + { + leftToolboxBackground.FadeIn(750, Easing.OutQuint); + rightToolboxBackground.FadeIn(750, Easing.OutQuint); + } + else + { + leftToolboxBackground.Delay(600).FadeTo(0.5f, 4000, Easing.OutQuint); + rightToolboxBackground.Delay(600).FadeTo(0.5f, 4000, Easing.OutQuint); + } }, true); } diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 6118adc0d7..dd56752119 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -82,10 +82,13 @@ namespace osu.Game.Screens.Edit saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true); composerFocusMode.BindValueChanged(_ => { - float targetAlpha = composerFocusMode.Value ? 0.5f : 1; - foreach (var c in this.ChildrenOfType()) - c.Background.FadeTo(targetAlpha, 400, Easing.OutQuint); + { + if (!composerFocusMode.Value) + c.Background.FadeIn(750, Easing.OutQuint); + else + c.Background.Delay(600).FadeTo(0.5f, 4000, Easing.OutQuint); + } }, true); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index cee7212a5d..ff92e658d9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -132,7 +132,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - composerFocusMode.BindValueChanged(_ => timelineBackground.FadeTo(composerFocusMode.Value ? 0.5f : 1, 400, Easing.OutQuint), true); + composerFocusMode.BindValueChanged(_ => + { + if (!composerFocusMode.Value) + timelineBackground.FadeIn(750, Easing.OutQuint); + else + timelineBackground.Delay(600).FadeTo(0.5f, 4000, Easing.OutQuint); + }, true); } } } From 50818da166110bbaadf47ad7c27d05dd9c5008ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:33:00 +0200 Subject: [PATCH 131/670] Ensure timeline ticks aren't hidden by other pieces Addresses https://github.com/ppy/osu/issues/28667. --- .../Compose/Components/BeatDivisorControl.cs | 6 ++--- .../Compose/Components/Timeline/Timeline.cs | 12 +++++++--- .../Timeline/TimelineTickDisplay.cs | 23 +++++++++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index faab5e7f78..1d8266d610 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { new TextFlowContainer(s => s.Font = s.Font.With(size: 14)) { - Padding = new MarginPadding { Horizontal = 15, Vertical = 8 }, + Padding = new MarginPadding { Horizontal = 15, Vertical = 2 }, Text = "beat snap", RelativeSizeAxes = Axes.X, TextAnchor = Anchor.TopCentre, @@ -159,7 +159,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 40), new Dimension(GridSizeMode.Absolute, 20), new Dimension(GridSizeMode.Absolute, 15) } @@ -526,7 +526,7 @@ namespace osu.Game.Screens.Edit.Compose.Components AlwaysDisplayed = alwaysDisplayed; Divisor = divisor; - Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); + Size = new Vector2(6f, 18) * BindableBeatDivisor.GetSize(divisor); Alpha = alwaysDisplayed ? 1 : 0; InternalChild = new Box { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index fa9964b104..05e44d4737 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Cached] public partial class Timeline : ZoomableScrollContainer, IPositionSnapProvider { - private const float timeline_height = 72; + private const float timeline_height = 80; private const float timeline_expanded_height = 94; private readonly Drawable userContent; @@ -97,6 +97,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // We don't want the centre marker to scroll AddInternal(centreMarker = new CentreMarker()); + ticks = new TimelineTickDisplay + { + Padding = new MarginPadding { Vertical = 2, }, + }; + AddRange(new Drawable[] { controlPoints = new TimelineControlPointDisplay @@ -104,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X, Height = timeline_expanded_height, }, + ticks, mainContent = new Container { RelativeSizeAxes = Axes.X, @@ -120,7 +126,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline HighColour = colours.BlueDarker, }, centreMarker.CreateProxy(), - ticks = new TimelineTickDisplay(), + ticks.CreateProxy(), new Box { Name = "zero marker", @@ -175,7 +181,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (visible.NewValue) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); - mainContent.MoveToY(20, 200, Easing.OutQuint); + mainContent.MoveToY(15, 200, Easing.OutQuint); // delay the fade in else masking looks weird. controlPoints.Delay(180).FadeIn(400, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index e16c8519e5..8de5087850 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -41,16 +42,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both; } + private readonly BindableBool showTimingChanges = new BindableBool(true); + private readonly Cached tickCache = new Cached(); [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager configManager) { beatDivisor.BindValueChanged(_ => invalidateTicks()); if (changeHandler != null) // currently this is the best way to handle any kind of timing changes. changeHandler.OnStateChange += invalidateTicks; + + configManager.BindWith(OsuSetting.EditorTimelineShowTimingChanges, showTimingChanges); + showTimingChanges.BindValueChanged(_ => invalidateTicks()); } private void invalidateTicks() @@ -142,7 +148,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var line = getNextUsableLine(); line.X = xPos; line.Width = PointVisualisation.MAX_WIDTH * size.X; - line.Height = 0.9f * size.Y; + + if (showTimingChanges.Value) + { + line.Anchor = Anchor.BottomLeft; + line.Origin = Anchor.BottomCentre; + line.Height = 0.7f + size.Y * 0.28f; + } + else + { + line.Anchor = Anchor.CentreLeft; + line.Origin = Anchor.Centre; + line.Height = 0.92f + size.Y * 0.07f; + } + line.Colour = colour; } From 29b89486097bc3c5877e32d78817f2d3f46d0d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:33:32 +0200 Subject: [PATCH 132/670] Slim down bottom timeline This removes the BPM display, which is commonly cited to have no functional purpose by users, and reduces the height of the bottom bar in exchange for more space for the playfield. --- osu.Game/Screens/Edit/BottomBar.cs | 2 +- .../Edit/Components/PlaybackControl.cs | 4 +-- .../Edit/Components/TimeInfoContainer.cs | 26 +------------------ osu.Game/Screens/Edit/Editor.cs | 2 +- 4 files changed, 5 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 6118adc0d7..fb233381aa 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.X; - Height = 60; + Height = 40; Masking = true; EdgeEffect = new EdgeEffectParameters diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 6319dc892e..9fe6160ab4 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -46,8 +46,8 @@ namespace osu.Game.Screens.Edit.Components { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Scale = new Vector2(1.4f), - IconScale = new Vector2(1.4f), + Scale = new Vector2(1.2f), + IconScale = new Vector2(1.2f), Icon = FontAwesome.Regular.PlayCircle, Action = togglePause, }, diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 7c03198ec0..37facb3b95 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -17,8 +17,6 @@ namespace osu.Game.Screens.Edit.Components { public partial class TimeInfoContainer : BottomBarContainer { - private OsuSpriteText bpm = null!; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -26,38 +24,16 @@ namespace osu.Game.Screens.Edit.Components private EditorClock editorClock { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider) { Background.Colour = colourProvider.Background5; Children = new Drawable[] { new TimestampControl(), - bpm = new OsuSpriteText - { - Colour = colours.Orange1, - Anchor = Anchor.CentreLeft, - Font = OsuFont.Torus.With(size: 18, weight: FontWeight.SemiBold), - Position = new Vector2(2, 5), - } }; } - private double? lastBPM; - - protected override void Update() - { - base.Update(); - - double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM; - - if (lastBPM != newBPM) - { - lastBPM = newBPM; - bpm.Text = @$"{newBPM:0} BPM"; - } - } - private partial class TimestampControl : OsuClickableContainer { private Container hoverLayer = null!; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a8a28ef0b8..3f117ebcff 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -325,7 +325,7 @@ namespace osu.Game.Screens.Edit { Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 60 }, + Padding = new MarginPadding { Top = 40, Bottom = 40 }, Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, From 5d5dd0de00feb6a06250f6fc427c343d39492b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 8 Jul 2024 12:41:23 +0200 Subject: [PATCH 133/670] Redesign bottom timeline pieces to match stable better --- .../ControlPoints/EffectControlPoint.cs | 2 +- .../ControlPoints/TimingControlPoint.cs | 2 +- .../Timelines/Summary/Parts/BookmarkPart.cs | 1 + .../Parts/ControlPointVisualisation.cs | 4 +--- .../Summary/Parts/EffectPointVisualisation.cs | 9 +++++---- .../Summary/Parts/GroupVisualisation.cs | 12 ++--------- .../Timelines/Summary/Parts/MarkerPart.cs | 20 ++----------------- .../Summary/Parts/PreviewTimePart.cs | 1 + .../Timelines/Summary/SummaryTimeline.cs | 7 +++---- .../Components/Timeline/CentreMarker.cs | 6 +++--- 10 files changed, 20 insertions(+), 44 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index d48ed957ee..4b73994dcf 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => ScrollSpeedBindable.Value = value; } - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; /// /// Whether this control point enables Kiai mode. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 9ac361cffe..3360b1d1fa 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -26,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Orange1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red1; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 3102bf7c06..ea71f24e9c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public BookmarkVisualisation(double startTime) : base(startTime) { + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 12620963e1..9ba0cac53f 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -16,9 +16,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public ControlPointVisualisation(ControlPoint point) { Point = point; - - Height = 0.25f; - Origin = Anchor.TopCentre; + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index bf87470e01..a3885bc2cc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -6,9 +6,9 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { @@ -91,12 +91,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Width = (float)(nextControlPoint.Time - effect.Time); - AddInternal(new PointVisualisation + AddInternal(new Circle { RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, Width = 1, - Height = 0.25f, + Height = 0.75f, Depth = float.MaxValue, Colour = effect.GetRepresentingColour(colours).Darken(0.5f), }); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b39365277f..2c806be162 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -39,19 +39,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts switch (point) { case TimingControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0, }); - break; - - case DifficultyControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0.25f, }); - break; - - case SampleControlPoint: - AddInternal(new ControlPointVisualisation(point) { Y = 0.5f, }); + AddInternal(new ControlPointVisualisation(point)); break; case EffectControlPoint effect: - AddInternal(new EffectPointVisualisation(effect) { Y = 0.75f }); + AddInternal(new EffectPointVisualisation(effect)); break; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index ff707407dd..21b3b38388 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Game.Graphics; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -73,8 +73,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public MarkerVisualisation() { - const float box_height = 4; - Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; RelativePositionAxes = Axes.X; @@ -82,32 +80,18 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts AutoSizeAxes = Axes.X; InternalChildren = new Drawable[] { - new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(14, box_height), - }, new Triangle { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Scale = new Vector2(1, -1), Size = new Vector2(10, 5), - Y = box_height, }, new Triangle { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = new Vector2(10, 5), - Y = -box_height, - }, - new Box - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(14, box_height), }, new Box { @@ -121,7 +105,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Red1; + private void load(OverlayColourProvider colours) => Colour = colours.Highlight1; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index c63bb7ac24..407173034e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public PreviewTimeVisualisation(double time) : base(time) { + Width = 2; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 6199cefb57..92012936bc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -23,13 +23,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Children = new Drawable[] { - new MarkerPart { RelativeSizeAxes = Axes.Both }, new ControlPointPart { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, - Y = -10, Height = 0.35f }, new BookmarkPart @@ -80,8 +78,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Height = 0.10f - } + Height = 0.15f + }, + new MarkerPart { RelativeSizeAxes = Axes.Both }, }; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 74786cc0c9..be1888684e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -5,7 +5,7 @@ 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.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -44,9 +44,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colours) { - Colour = colours.Red1; + Colour = colours.Highlight1; } } } From 63b43279780c35ed51e1a3799f298ca770135b5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 16:58:35 +0900 Subject: [PATCH 134/670] Ensure beatmap set is always detached when updating Slight performance improvement by doing the detach as early as possible. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9d61e2a977..e49b1bc3b5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -417,16 +417,23 @@ namespace osu.Game.Screens.Select } } - public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => + public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - updateBeatmapSet(beatmapSet); - invalidateAfterChange(); - }); + beatmapSet = beatmapSet.Detach(); + + Schedule(() => + { + updateBeatmapSet(beatmapSet); + invalidateAfterChange(); + }); + } private void updateBeatmapSet(BeatmapSetInfo beatmapSet) { + beatmapSet = beatmapSet.Detach(); + originalBeatmapSetsDetached.RemoveAll(set => set.ID == beatmapSet.ID); - originalBeatmapSetsDetached.Add(beatmapSet.Detach()); + originalBeatmapSetsDetached.Add(beatmapSet); var newSets = new List(); From 920c0e4d25999acef016eb1ad5ccb1031688fd79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 17:04:51 +0900 Subject: [PATCH 135/670] Fix deleted beatmap sets potentially reappearing due to pending update requests --- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e49b1bc3b5..b17c74e473 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -267,7 +267,7 @@ namespace osu.Game.Screens.Select subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); } - private readonly HashSet setsRequiringUpdate = new HashSet(); + private readonly HashSet setsRequiringUpdate = new HashSet(); private readonly HashSet setsRequiringRemoval = new HashSet(); private void beatmapSetsChanged(IRealmCollection sender, ChangeSet? changes) @@ -280,6 +280,7 @@ namespace osu.Game.Screens.Select { realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); @@ -289,18 +290,24 @@ namespace osu.Game.Screens.Select { foreach (int i in changes.DeletedIndices.OrderDescending()) { - setsRequiringRemoval.Add(realmBeatmapSets[i]); + Guid id = realmBeatmapSets[i]; + + setsRequiringRemoval.Add(id); + setsRequiringUpdate.Remove(id); + realmBeatmapSets.RemoveAt(i); } foreach (int i in changes.InsertedIndices) { - realmBeatmapSets.Insert(i, sender[i].ID); - setsRequiringUpdate.Add(sender[i].Detach()); + Guid id = sender[i].ID; + + realmBeatmapSets.Insert(i, id); + setsRequiringUpdate.Add(id); } foreach (int i in changes.NewModifiedIndices) - setsRequiringUpdate.Add(sender[i].Detach()); + setsRequiringUpdate.Add(sender[i].ID); } Scheduler.AddOnce(processBeatmapChanges); @@ -316,7 +323,7 @@ namespace osu.Game.Screens.Select { foreach (var set in setsRequiringRemoval) removeBeatmapSet(set); - foreach (var set in setsRequiringUpdate) updateBeatmapSet(set); + foreach (var set in setsRequiringUpdate) updateBeatmapSet(fetchFromID(set)!); if (setsRequiringRemoval.Count > 0 && SelectedBeatmapInfo != null) { @@ -326,7 +333,7 @@ namespace osu.Game.Screens.Select // To handle the beatmap update flow, attempt to track selection changes across delete-insert transactions. // When an update occurs, the previous beatmap set is either soft or hard deleted. // Check if the current selection was potentially deleted by re-querying its validity. - bool selectedSetMarkedDeleted = realm.Run(r => r.Find(SelectedBeatmapSet.ID)?.DeletePending != false); + bool selectedSetMarkedDeleted = fetchFromID(SelectedBeatmapSet.ID)?.DeletePending != false; if (selectedSetMarkedDeleted && setsRequiringUpdate.Any()) { @@ -334,7 +341,7 @@ namespace osu.Game.Screens.Select // This relies on the full update operation being in a single transaction, so please don't change that. foreach (var set in setsRequiringUpdate) { - foreach (var beatmapInfo in set.Beatmaps) + foreach (var beatmapInfo in fetchFromID(set)!.Beatmaps) { if (!((IBeatmapMetadataInfo)beatmapInfo.Metadata).Equals(SelectedBeatmapInfo.Metadata)) continue; @@ -349,7 +356,7 @@ namespace osu.Game.Screens.Select // If a direct selection couldn't be made, it's feasible that the difficulty name (or beatmap metadata) changed. // Let's attempt to follow set-level selection anyway. - SelectBeatmap(setsRequiringUpdate.First().Beatmaps.First()); + SelectBeatmap(fetchFromID(setsRequiringUpdate.First())!.Beatmaps.First()); } } } @@ -361,6 +368,8 @@ namespace osu.Game.Screens.Select setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); + + BeatmapSetInfo? fetchFromID(Guid id) => realm.Realm.Find(id); } private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) From ca4c0aa7e211471f26ee5aa776cd85bc786577c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jul 2024 10:57:21 +0200 Subject: [PATCH 136/670] Remove unused using --- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 9ba0cac53f..47169481e2 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; From 0e2e44a2f50349e45d370a043fe1b5d4f493bb74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 20:34:44 +0900 Subject: [PATCH 137/670] Add failing test case showing editor save then delete failure --- .../TestSceneBeatmapEditorNavigation.cs | 42 +++++++++++++++++-- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index e3a8e575f8..1ac4bb347b 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -29,6 +29,35 @@ namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene { + [Test] + public void TestSaveThenDeleteActuallyDeletesAtSongSelect() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + makeMetadataChange(); + + AddAssert("save", () => Game.ChildrenOfType().Single().Save()); + + AddStep("delete beatmap", () => Game.BeatmapManager.Delete(beatmapSet)); + + AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.Beatmap.Value is DummyWorkingBeatmap); + } + [Test] public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave() { @@ -47,6 +76,15 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + makeMetadataChange(commit: false); + + AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + + AddUntilStep("save dialog displayed", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog); + } + + private void makeMetadataChange(bool commit = true) + { AddStep("change to song setup", () => InputManager.Key(Key.F4)); TextBox textbox = null!; @@ -77,9 +115,7 @@ namespace osu.Game.Tests.Visual.Navigation InputManager.Keys(PlatformAction.Paste); }); - AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); - - AddUntilStep("save dialog displayed", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog); + if (commit) AddStep("commit", () => InputManager.Key(Key.Enter)); } [Test] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a8a28ef0b8..27d0392b1e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -521,7 +521,7 @@ namespace osu.Game.Screens.Edit /// Saves the currently edited beatmap. /// /// Whether the save was successful. - protected bool Save() + internal bool Save() { if (!canSave) { From 123d3d2ff814bb0fca56334de90c7a8ba5f1e7f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 20:44:55 +0900 Subject: [PATCH 138/670] Add similar special case for insert after removal --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b17c74e473..3f9e676068 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -302,8 +302,10 @@ namespace osu.Game.Screens.Select { Guid id = sender[i].ID; - realmBeatmapSets.Insert(i, id); + setsRequiringRemoval.Remove(id); setsRequiringUpdate.Add(id); + + realmBeatmapSets.Insert(i, id); } foreach (int i in changes.NewModifiedIndices) From ec9040798f233da015e928cd3d2b0273ec567be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jul 2024 13:52:36 +0200 Subject: [PATCH 139/670] Run stacking when performing movement in osu! composer Closes https://github.com/ppy/osu/issues/28635. --- .../Edit/OsuSelectionHandler.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 41d47d31d0..9b4b77b625 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -50,12 +50,33 @@ namespace osu.Game.Rulesets.Osu.Edit { var hitObjects = selectedMovableObjects; + var localDelta = this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); + + // this conditional is a rather ugly special case for stacks. + // as it turns out, adding the `EditorBeatmap.Update()` call at the end of this would cause stacked objects to jitter when moved around + // (they would stack and then unstack every frame). + // the reason for that is that the selection handling abstractions are not aware of the distinction between "displayed" and "actual" position + // which is unique to osu! due to stacking being applied as a post-processing step. + // therefore, the following loop would occur: + // - on frame 1 the blueprint is snapped to the stack's baseline position. `EditorBeatmap.Update()` applies stacking successfully, + // the blueprint moves up the stack from its original drag position. + // - on frame 2 the blueprint's position is now the *stacked* position, which is interpreted higher up as *manually performing an unstack* + // to the blueprint's unstacked position (as the machinery higher up only cares about differences in screen space position). + if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset))) + return true; + // this will potentially move the selection out of bounds... foreach (var h in hitObjects) - h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta); + h.Position += localDelta; // but this will be corrected. moveSelectionInBounds(); + + // update all of the objects in order to update stacking. + // in particular, this causes stacked objects to instantly unstack on drag. + foreach (var h in hitObjects) + EditorBeatmap.Update(h); + return true; } From 9cc0e0137b5c7d0c5486fd993936ea34cec7c1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 9 Jul 2024 13:58:58 +0200 Subject: [PATCH 140/670] Snap to stack in osu! composer when dragging to any of the items on it Previously it would be required to drag to the starting position of the stack which feels weird. --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 26bd96cc3a..3c1d0fbb1c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -295,6 +295,12 @@ namespace osu.Game.Rulesets.Osu.Edit if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius) { + // if the snap target is a stacked object, snap to its unstacked position rather than its stacked position. + // this is intended to make working with stacks easier (because thanks to this, you can drag an object to any + // of the items on the stack to add an object to it, rather than having to drag to the position of the *first* object on it at all times). + if (b.Item is OsuHitObject osuObject && osuObject.StackOffset != Vector2.Zero) + closestSnapPosition = b.ToScreenSpace(b.ToLocalSpace(closestSnapPosition) - osuObject.StackOffset); + // only return distance portion, since time is not really valid snapResult = new SnapResult(closestSnapPosition, null, playfield); return true; From 8c81ba3357dc91e2329d88a93240abfd7efd0c55 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 9 Jul 2024 14:36:24 -0700 Subject: [PATCH 141/670] Fix preview track persisting to play after leaving multi/playlists room --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 4eb092d08b..515d9fc7a5 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -80,6 +80,9 @@ namespace osu.Game.Screens.OnlinePlay.Match [Resolved(canBeNull: true)] protected OnlinePlayScreen ParentScreen { get; private set; } + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -483,6 +486,8 @@ namespace osu.Game.Screens.OnlinePlay.Match { UserModsSelectOverlay.Hide(); endHandlingTrack(); + + previewTrackManager.StopAnyPlaying(this); } private void endHandlingTrack() From 4e1240c349169b3493d799f85427f65a1c53717b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 13:54:27 +0900 Subject: [PATCH 142/670] Migrate `ShearedOverlayContainer` to NRT --- .../Overlays/Mods/ShearedOverlayContainer.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index c9c3c62404..d3326cb86b 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,16 +27,15 @@ namespace osu.Game.Overlays.Mods /// /// The overlay's header. /// - protected ShearedOverlayHeader Header { get; private set; } + protected ShearedOverlayHeader Header { get; private set; } = null!; /// /// The overlay's footer. /// - protected Container Footer { get; private set; } + protected Container Footer { get; private set; } = null!; - [Resolved(canBeNull: true)] - [CanBeNull] - private ScreenFooter footer { get; set; } + [Resolved] + private ScreenFooter? footer { get; set; } // todo: very temporary property that will be removed once ModSelectOverlay and FirstRunSetupOverlay are updated to use new footer. public virtual bool UseNewFooter => false; @@ -48,17 +44,17 @@ namespace osu.Game.Overlays.Mods /// A container containing all content, including the header and footer. /// May be used for overlay-wide animations. /// - protected Container TopLevelContent { get; private set; } + protected Container TopLevelContent { get; private set; } = null!; /// /// A container for content that is to be displayed between the header and footer. /// - protected Container MainAreaContent { get; private set; } + protected Container MainAreaContent { get; private set; } = null!; /// /// A container for content that is to be displayed inside the footer. /// - protected Container FooterContent { get; private set; } + protected Container FooterContent { get; private set; } = null!; protected override bool StartHidden => true; From f2810193588f55ebc91f17ce4e39a68be7ca54fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 13:58:50 +0900 Subject: [PATCH 143/670] Rename method to match provided argument --- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 4 ++-- osu.Game/Screens/Footer/ScreenFooter.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index d3326cb86b..aed9b395f6 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Mods if (UseNewFooter && footer != null) { - footer.SetOverlayContent(this); + footer.SetActiveOverlayContainer(this); if (footer.State.Value == Visibility.Hidden) { @@ -175,7 +175,7 @@ namespace osu.Game.Overlays.Mods if (UseNewFooter && footer != null) { - footer.ClearOverlayContent(); + footer.ClearActiveOverlayContainer(); if (hideFooterOnPopOut) { diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index cef891f8c0..f9a6d54b96 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Footer temporarilyHiddenButtons.Clear(); overlays.Clear(); - ClearOverlayContent(); + ClearActiveOverlayContainer(); var oldButtons = buttonsFlow.ToArray(); @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Footer private Container? contentContainer; private readonly List temporarilyHiddenButtons = new List(); - public void SetOverlayContent(ShearedOverlayContainer overlay) + public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) { if (contentContainer != null) { @@ -213,7 +213,7 @@ namespace osu.Game.Screens.Footer content.Show(); } - public void ClearOverlayContent() + public void ClearActiveOverlayContainer() { if (contentContainer == null) return; From 002679ebb0e75ee6d72e9e0e8bfb08a3be4314e9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:12:52 +0300 Subject: [PATCH 144/670] Ask for `VisibilityContainer` explicitly --- osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs | 2 +- osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 2 +- osu.Game/Screens/Footer/ScreenFooter.cs | 2 +- osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs | 3 ++- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index de2026e538..c8cf6a6ffb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.UserInterface return false; } - public override Drawable CreateFooterContent() => new TestFooterContent(); + public override VisibilityContainer CreateFooterContent() => new TestFooterContent(); public partial class TestFooterContent : VisibilityContainer { diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 6412297663..2c8ceba82c 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -150,7 +150,7 @@ namespace osu.Game.Overlays private FirstRunSetupFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() + public override VisibilityContainer CreateFooterContent() { currentFooterContent = new FirstRunSetupFooterContent { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index bd04a1f6b3..da93539679 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -256,7 +256,7 @@ namespace osu.Game.Overlays.Mods private ModSelectFooterContent? currentFooterContent; - public override Drawable CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 9e5a336c17..9ea98c1ae4 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Mods /// /// Creates content to be displayed on the game-wide footer. /// - public virtual Drawable CreateFooterContent() => Empty(); + public virtual VisibilityContainer? CreateFooterContent() => null; /// /// Invoked when the back button in the footer is pressed. diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index a841f2a50b..4464b9d7da 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Footer updateColourScheme(overlay.ColourProvider.ColourScheme); - var content = overlay.CreateFooterContent(); + var content = overlay.CreateFooterContent() ?? Empty(); Add(contentContainer = new Container { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 0ed45161f2..2b3ab94916 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -6,6 +6,7 @@ using osu.Game.Overlays; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -34,7 +35,7 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - public override Drawable CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, From f0a7a3f85657caf09c639bd5cef154d339e5b9de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:51:49 +0300 Subject: [PATCH 145/670] Add failing test case for edge case --- .../UserInterface/TestSceneScreenFooter.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs index c8cf6a6ffb..a4cf8a276f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenFooter.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface { private DependencyProvidingContainer contentContainer = null!; private ScreenFooter screenFooter = null!; - private TestModSelectOverlay overlay = null!; + private TestModSelectOverlay modOverlay = null!; [SetUp] public void SetUp() => Schedule(() => @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, Children = new Drawable[] { - overlay = new TestModSelectOverlay(), + modOverlay = new TestModSelectOverlay(), new PopoverContainer { RelativeSizeAxes = Axes.Both, @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface screenFooter.SetButtons(new ScreenFooterButton[] { - new ScreenFooterButtonMods(overlay) { Current = SelectedMods }, + new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods }, new ScreenFooterButtonRandom(), new ScreenFooterButtonOptions(), }); @@ -178,6 +178,24 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden); } + [Test] + public void TestLoadOverlayAfterFooterIsDisplayed() + { + TestShearedOverlayContainer externalOverlay = null!; + + AddStep("show mod overlay", () => modOverlay.Show()); + AddUntilStep("mod footer content shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.True); + + AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer())); + AddUntilStep("wait for load", () => externalOverlay.IsLoaded); + AddAssert("mod footer content still shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.True); + AddAssert("external overlay content not shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + + AddStep("hide mod overlay", () => modOverlay.Hide()); + AddUntilStep("mod footer content hidden", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + AddAssert("external overlay content still not shown", () => this.ChildrenOfType().SingleOrDefault()?.IsPresent, () => Is.Not.True); + } + private partial class TestModSelectOverlay : UserModSelectOverlay { protected override bool ShowPresets => true; From 337f05f9a454019bed39d6a32f7da94ac4be43fb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 11:54:04 +0300 Subject: [PATCH 146/670] Fix loading (but not showing) a sheared overlay hiding displayed footer content Identified by tests. See https://github.com/ppy/osu/actions/runs/9869382635/job/27253010485 & https://github.com/ppy/osu/actions/runs/9869382635/job/27253009622. This change also prevents the initial `PopOut` call in overlays from calling `clearActiveOverlayContainer`, since it's not in the update thread and it's never meant to be called at that point anyway (it's supposed to be accompanied by a previous `PopIn` call adding the footer content). --- .../Overlays/Mods/ShearedOverlayContainer.cs | 7 +++-- osu.Game/Screens/Footer/ScreenFooter.cs | 26 ++++++++++--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 9ea98c1ae4..8c6b9e805b 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -123,6 +124,7 @@ namespace osu.Game.Overlays.Mods return base.OnClick(e); } + private IDisposable? activeOverlayRegistration; private bool hideFooterOnPopOut; protected override void PopIn() @@ -135,7 +137,7 @@ namespace osu.Game.Overlays.Mods if (footer != null) { - footer.SetActiveOverlayContainer(this); + activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this); if (footer.State.Value == Visibility.Hidden) { @@ -156,7 +158,8 @@ namespace osu.Game.Overlays.Mods if (footer != null) { - footer.ClearActiveOverlayContainer(); + activeOverlayRegistration?.Dispose(); + activeOverlayRegistration = null; if (hideFooterOnPopOut) { diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 4464b9d7da..f8d222e510 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Footer temporarilyHiddenButtons.Clear(); overlays.Clear(); - ClearActiveOverlayContainer(); + clearActiveOverlayContainer(); var oldButtons = buttonsFlow.ToArray(); @@ -187,14 +187,15 @@ namespace osu.Game.Screens.Footer private ShearedOverlayContainer? activeOverlay; private Container? contentContainer; + private readonly List temporarilyHiddenButtons = new List(); - public void SetActiveOverlayContainer(ShearedOverlayContainer overlay) + public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay) { - if (contentContainer != null) + if (activeOverlay != null) { throw new InvalidOperationException(@"Cannot set overlay content while one is already present. " + - $@"The previous overlay whose content is {contentContainer.Child.GetType().Name} should be hidden first."); + $@"The previous overlay ({activeOverlay.GetType().Name}) should be hidden first."); } activeOverlay = overlay; @@ -232,29 +233,30 @@ namespace osu.Game.Screens.Footer this.Delay(60).Schedule(() => content.Show()); else content.Show(); + + return new InvokeOnDisposal(clearActiveOverlayContainer); } - public void ClearActiveOverlayContainer() + private void clearActiveOverlayContainer() { - if (contentContainer == null) + if (activeOverlay == null) return; + Debug.Assert(contentContainer != null); contentContainer.Child.Hide(); double timeUntilRun = contentContainer.Child.LatestTransformEndTime - Time.Current; - Container expireTarget = contentContainer; - contentContainer = null; - activeOverlay = null; - for (int i = 0; i < temporarilyHiddenButtons.Count; i++) makeButtonAppearFromBottom(temporarilyHiddenButtons[i], 0); temporarilyHiddenButtons.Clear(); - expireTarget.Delay(timeUntilRun).Expire(); - updateColourScheme(OverlayColourScheme.Aquamarine); + + contentContainer.Delay(timeUntilRun).Expire(); + contentContainer = null; + activeOverlay = null; } private void updateColourScheme(OverlayColourScheme colourScheme) From d3c66e240459f6d563476c5c02c223e12252819c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Jul 2024 12:07:13 +0900 Subject: [PATCH 147/670] Add basic flow for mounting beatmaps for external editing --- osu.Game/Beatmaps/BeatmapManager.cs | 3 ++ osu.Game/Database/ExternalEditOperation.cs | 48 +++++++++++++++++++ osu.Game/Database/IModelImporter.cs | 6 +++ .../Database/RealmArchiveModelImporter.cs | 24 ++++++++++ osu.Game/Scoring/ScoreManager.cs | 3 +- osu.Game/Screens/Edit/Editor.cs | 43 +++++++++++++++++ osu.Game/Skinning/SkinManager.cs | 2 + 7 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Database/ExternalEditOperation.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0610f7f6fb..e90b3c703f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -415,6 +415,9 @@ namespace osu.Game.Beatmaps public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) => beatmapImporter.ImportAsUpdate(notification, importTask, original); + public Task> BeginExternalEditing(BeatmapSetInfo model) => + beatmapImporter.BeginExternalEditing(model); + public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm)); public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm)); diff --git a/osu.Game/Database/ExternalEditOperation.cs b/osu.Game/Database/ExternalEditOperation.cs new file mode 100644 index 0000000000..ab74cba7d5 --- /dev/null +++ b/osu.Game/Database/ExternalEditOperation.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Threading.Tasks; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Database +{ + public class ExternalEditOperation where TModel : class, IHasGuidPrimaryKey + { + public readonly string MountedPath; + + private readonly IModelImporter importer; + private readonly TModel original; + + private bool isMounted; + + public ExternalEditOperation(IModelImporter importer, TModel original, string path) + { + this.importer = importer; + this.original = original; + + MountedPath = path; + + isMounted = true; + } + + public async Task?> Finish() + { + if (!Directory.Exists(MountedPath) || !isMounted) + return null; + + Live? imported = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(MountedPath), original) + .ConfigureAwait(false); + + try + { + Directory.Delete(MountedPath, true); + } + catch { } + + isMounted = false; + + return imported; + } + } +} diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index dcbbad0d35..c2e5517f2a 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -34,6 +34,12 @@ namespace osu.Game.Database /// The imported model. Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original); + /// + /// Mount all files for a to a temporary directory to allow for external editing. + /// + /// The to mount. + public Task> BeginExternalEditing(TModel model); + /// /// A user displayable name for the model type associated with this manager. /// diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 0014e246dc..38df2ac1dc 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -179,6 +179,30 @@ namespace osu.Game.Database public virtual Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException(); + public async Task> BeginExternalEditing(TModel model) + { + string mountedPath = Path.Join(Path.GetTempPath(), model.Hash); + + if (Directory.Exists(mountedPath)) + Directory.Delete(mountedPath, true); + + Directory.CreateDirectory(mountedPath); + + foreach (var realmFile in model.Files) + { + string sourcePath = Files.Storage.GetFullPath(realmFile.File.GetStoragePath()); + string destinationPath = Path.Join(mountedPath, realmFile.Filename); + + Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!); + + using (var inStream = Files.Storage.GetStream(sourcePath)) + using (var outStream = File.Create(destinationPath)) + await inStream.CopyToAsync(outStream).ConfigureAwait(false); + } + + return new ExternalEditOperation(this, model, mountedPath); + } + /// /// Import one from the filesystem and delete the file on success. /// Note that this bypasses the UI flow and should only be used for special cases or testing. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index df4735b5e6..e3601fe91e 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -15,10 +15,10 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.IO.Archives; +using osu.Game.Online.API; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; -using osu.Game.Online.API; using osu.Game.Scoring.Legacy; namespace osu.Game.Scoring @@ -214,6 +214,7 @@ namespace osu.Game.Scoring } public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); + public Task> BeginExternalEditing(ScoreInfo model) => scoreImporter.BeginExternalEditing(model); public Live? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => scoreImporter.ImportModel(item, archive, parameters, cancellationToken); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 27d0392b1e..ff8cf3997e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; @@ -13,6 +14,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -22,6 +24,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; @@ -820,6 +823,9 @@ namespace osu.Game.Screens.Edit resetTrack(); + fileMountOperation?.Dispose(); + fileMountOperation = null; + refetchBeatmap(); return base.OnExiting(e); @@ -1095,6 +1101,11 @@ namespace osu.Game.Screens.Edit lastSavedHash = changeHandler?.CurrentStateHash; } + private EditorMenuItem mountFilesItem; + + [CanBeNull] + private Task> fileMountOperation; + private IEnumerable createFileMenuItems() { yield return createDifficultyCreationMenu(); @@ -1112,12 +1123,44 @@ namespace osu.Game.Screens.Edit var export = createExportMenu(); saveRelatedMenuItems.AddRange(export.Items); yield return export; + + yield return mountFilesItem = new EditorMenuItem("Mount files", MenuItemType.Standard, mountFiles); } yield return new OsuMenuItemSpacer(); yield return new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit); } + [Resolved] + private GameHost gameHost { get; set; } + + private void mountFiles() + { + if (fileMountOperation == null) + { + Save(); + + fileMountOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); + mountFilesItem.Text.Value = "Dismount files"; + + fileMountOperation.ContinueWith(t => + { + var operation = t.GetResultSafely(); + + // Ensure the trailing separator is present in order to show the folder contents. + gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); + }); + } + else + { + fileMountOperation.GetResultSafely().Finish().ContinueWith(t => Schedule(() => + { + fileMountOperation = null; + SwitchToDifficulty(t.GetResultSafely().Value.Detach().Beatmaps.First()); + })); + } + } + private EditorMenuItem createExportMenu() { var exportItems = new List diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 59c2a8bca0..4f816d88d2 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -312,6 +312,8 @@ namespace osu.Game.Skinning public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) => skinImporter.ImportAsUpdate(notification, task, original); + public Task> BeginExternalEditing(SkinInfo model) => skinImporter.BeginExternalEditing(model); + public Task> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) => skinImporter.Import(task, parameters, cancellationToken); From 118162c6315a7d93023dac06d8d253e56b0073e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 20:57:29 +0900 Subject: [PATCH 148/670] Add sub screen to limit user interactions --- osu.Game/Screens/Edit/Editor.cs | 59 +++++------------- osu.Game/Screens/Edit/ExternalEditScreen.cs | 68 +++++++++++++++++++++ 2 files changed, 84 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Screens/Edit/ExternalEditScreen.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ff8cf3997e..a675b41833 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; @@ -14,7 +13,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -24,7 +22,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Logging; -using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; @@ -823,9 +820,6 @@ namespace osu.Game.Screens.Edit resetTrack(); - fileMountOperation?.Dispose(); - fileMountOperation = null; - refetchBeatmap(); return base.OnExiting(e); @@ -1101,11 +1095,6 @@ namespace osu.Game.Screens.Edit lastSavedHash = changeHandler?.CurrentStateHash; } - private EditorMenuItem mountFilesItem; - - [CanBeNull] - private Task> fileMountOperation; - private IEnumerable createFileMenuItems() { yield return createDifficultyCreationMenu(); @@ -1124,43 +1113,15 @@ namespace osu.Game.Screens.Edit saveRelatedMenuItems.AddRange(export.Items); yield return export; - yield return mountFilesItem = new EditorMenuItem("Mount files", MenuItemType.Standard, mountFiles); + var externalEdit = new EditorMenuItem("Edit externally", MenuItemType.Standard, editExternally); + saveRelatedMenuItems.Add(externalEdit); + yield return externalEdit; } yield return new OsuMenuItemSpacer(); yield return new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit); } - [Resolved] - private GameHost gameHost { get; set; } - - private void mountFiles() - { - if (fileMountOperation == null) - { - Save(); - - fileMountOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); - mountFilesItem.Text.Value = "Dismount files"; - - fileMountOperation.ContinueWith(t => - { - var operation = t.GetResultSafely(); - - // Ensure the trailing separator is present in order to show the folder contents. - gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); - }); - } - else - { - fileMountOperation.GetResultSafely().Finish().ContinueWith(t => Schedule(() => - { - fileMountOperation = null; - SwitchToDifficulty(t.GetResultSafely().Value.Detach().Beatmaps.First()); - })); - } - } - private EditorMenuItem createExportMenu() { var exportItems = new List @@ -1172,6 +1133,14 @@ namespace osu.Game.Screens.Edit return new EditorMenuItem(CommonStrings.Export) { Items = exportItems }; } + private void editExternally() + { + Save(); + + var editOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); + this.Push(new ExternalEditScreen(editOperation, this)); + } + private void exportBeatmap(bool legacy) { if (HasUnsavedChanges) @@ -1303,7 +1272,11 @@ namespace osu.Game.Screens.Edit return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems }; } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); + public void SwitchToDifficulty(BeatmapInfo nextBeatmap) + { + switchingDifficulty = true; + loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset)); + } private void cancelExit() { diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs new file mode 100644 index 0000000000..79a10c6292 --- /dev/null +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -0,0 +1,68 @@ +#nullable enable +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit +{ + internal partial class ExternalEditScreen : OsuScreen + { + private readonly Task> fileMountOperation; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + private readonly Editor? editor; + + public ExternalEditScreen(Task> fileMountOperation, Editor editor) + { + this.fileMountOperation = fileMountOperation; + this.editor = editor; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + fileMountOperation.ContinueWith(t => + { + var operation = t.GetResultSafely>(); + + // Ensure the trailing separator is present in order to show the folder contents. + gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); + }); + + InternalChildren = new Drawable[] + { + new SettingsButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "end editing", + Action = finish, + } + }; + } + + private void finish() + { + fileMountOperation.GetResultSafely().Finish().ContinueWith(t => + { + Schedule(() => + { + editor?.SwitchToDifficulty(t.GetResultSafely>().Value.Detach().Beatmaps.First()); + }); + }); + } + } +} From 74aa05fa6ed5eec8bad5e2d6b0ccef0788b93677 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 21:20:29 +0900 Subject: [PATCH 149/670] Improve UX and styling of external edit screen --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 108 +++++++++++++++++--- 1 file changed, 92 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index 79a10c6292..047a4d442e 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -1,4 +1,3 @@ -#nullable enable // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. @@ -8,10 +7,17 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Overlays.Settings; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.Match.Components; +using osuTK; namespace osu.Game.Screens.Edit { @@ -22,36 +28,106 @@ namespace osu.Game.Screens.Edit [Resolved] private GameHost gameHost { get; set; } = null!; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + private readonly Editor? editor; + private ExternalEditOperation? operation; + public ExternalEditScreen(Task> fileMountOperation, Editor editor) { this.fileMountOperation = fileMountOperation; this.editor = editor; } + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Container + { + Masking = true, + CornerRadius = 20, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(20), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Beatmap is mounted externally", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + } + }, + new PurpleRoundedButton + { + Text = "Open folder", + Width = 350, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = open, + }, + new DangerousRoundedButton + { + Text = "Finish editing and import changes", + Width = 350, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = finish, + } + } + } + } + }; + } + protected override void LoadComplete() { base.LoadComplete(); fileMountOperation.ContinueWith(t => { - var operation = t.GetResultSafely>(); - - // Ensure the trailing separator is present in order to show the folder contents. - gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); + operation = t.GetResultSafely(); + Schedule(open); }); + } - InternalChildren = new Drawable[] - { - new SettingsButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "end editing", - Action = finish, - } - }; + private void open() + { + if (operation == null) + return; + + // Ensure the trailing separator is present in order to show the folder contents. + gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); } private void finish() From 27ab54882b16e83d3e487da0c19ebdd652d5875c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 21:50:33 +0900 Subject: [PATCH 150/670] Add loading segments and tidy things up --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 158 ++++++++++++++------ 1 file changed, 114 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index 047a4d442e..fd438eacb3 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -10,11 +11,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; @@ -31,10 +36,14 @@ namespace osu.Game.Screens.Edit [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - private readonly Editor? editor; + private readonly Editor editor; private ExternalEditOperation? operation; + private double timeLoaded; + + private FillFlowContainer flow = null!; + public ExternalEditScreen(Task> fileMountOperation, Editor editor) { this.fileMountOperation = fileMountOperation; @@ -60,64 +69,78 @@ namespace osu.Game.Screens.Edit Colour = colourProvider.Background5, RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + flow = new FillFlowContainer { Margin = new MarginPadding(20), AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Spacing = new Vector2(15), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Beatmap is mounted externally", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(15), - Children = new Drawable[] - { - } - }, - new PurpleRoundedButton - { - Text = "Open folder", - Width = 350, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = open, - }, - new DangerousRoundedButton - { - Text = "Finish editing and import changes", - Width = 350, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = finish, - } - } } } }; + + showSpinner("Exporting for edit..."); } protected override void LoadComplete() { base.LoadComplete(); + timeLoaded = Time.Current; + fileMountOperation.ContinueWith(t => { operation = t.GetResultSafely(); - Schedule(open); + + Scheduler.AddDelayed(() => + { + flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Beatmap is mounted externally", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new OsuTextFlowContainer + { + Padding = new MarginPadding(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 350, + AutoSizeAxes = Axes.Y, + Text = "Any changes made to the exported folder will be imported to the game, including file additions, modifications and deletions.", + }, + new PurpleRoundedButton + { + Text = "Open folder", + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = open, + Enabled = { Value = false } + }, + new DangerousRoundedButton + { + Text = "Finish editing and import changes", + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = finish, + Enabled = { Value = false } + } + }; + + Scheduler.AddDelayed(() => + { + foreach (var b in flow.ChildrenOfType()) + b.Enabled.Value = true; + open(); + }, 1000); + }, Math.Max(0, 1000 - (Time.Current - timeLoaded))); }); } @@ -130,15 +153,62 @@ namespace osu.Game.Screens.Edit gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); } + public override bool OnExiting(ScreenExitEvent e) + { + if (!fileMountOperation.IsCompleted) + return false; + + if (operation != null) + { + finish(); + return false; + } + + return base.OnExiting(e); + } + private void finish() { - fileMountOperation.GetResultSafely().Finish().ContinueWith(t => + showSpinner("Cleaning up..."); + + EditOperation!.Finish().ContinueWith(t => { Schedule(() => { - editor?.SwitchToDifficulty(t.GetResultSafely>().Value.Detach().Beatmaps.First()); + // Setting to null will allow exit to succeed. + operation = null; + + var beatmap = t.GetResultSafely(); + + if (beatmap == null) + this.Exit(); + else + editor.SwitchToDifficulty(beatmap.Value.Detach().Beatmaps.First()); }); }); } + + private void showSpinner(string text) + { + foreach (var b in flow.ChildrenOfType()) + b.Enabled.Value = false; + + flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = text, + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new LoadingSpinner + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + State = { Value = Visibility.Visible } + }, + }; + } } } From 3beca64cc514f4667340b004e6ee0553ca7cd92c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jul 2024 21:53:57 +0900 Subject: [PATCH 151/670] Attempt to stay on correct difficulty --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index fd438eacb3..ae5fad3ec0 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -169,6 +169,8 @@ namespace osu.Game.Screens.Edit private void finish() { + string originalDifficulty = editor.Beatmap.Value.Beatmap.BeatmapInfo.DifficultyName; + showSpinner("Cleaning up..."); EditOperation!.Finish().ContinueWith(t => @@ -178,12 +180,18 @@ namespace osu.Game.Screens.Edit // Setting to null will allow exit to succeed. operation = null; - var beatmap = t.GetResultSafely(); + Live? beatmap = t.GetResultSafely(); if (beatmap == null) this.Exit(); else - editor.SwitchToDifficulty(beatmap.Value.Detach().Beatmaps.First()); + { + var closestMatchingBeatmap = + beatmap.Value.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalDifficulty) + ?? beatmap.Value.Beatmaps.First(); + + editor.SwitchToDifficulty(closestMatchingBeatmap); + } }); }); } From 72091b43df03c996b9b5cbb7534e984426c7a29d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 14:34:25 +0900 Subject: [PATCH 152/670] Simplify editor navigation tests --- .../TestSceneBeatmapEditorNavigation.cs | 143 +++++------------- 1 file changed, 40 insertions(+), 103 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 1ac4bb347b..efdcde9161 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -29,30 +29,20 @@ namespace osu.Game.Tests.Visual.Navigation { public partial class TestSceneBeatmapEditorNavigation : OsuGameTestScene { + private BeatmapSetInfo beatmapSet = null!; + [Test] public void TestSaveThenDeleteActuallyDeletesAtSongSelect() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - + prepareBeatmap(); + openEditor(); makeMetadataChange(); - AddAssert("save", () => Game.ChildrenOfType().Single().Save()); + AddAssert("save", () => getEditor().Save()); AddStep("delete beatmap", () => Game.BeatmapManager.Delete(beatmapSet)); - AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + AddStep("exit", () => getEditor().Exit()); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.Beatmap.Value is DummyWorkingBeatmap); @@ -61,24 +51,14 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestChangeMetadataExitWhileTextboxFocusedPromptsSave() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + prepareBeatmap(); + openEditor(); makeMetadataChange(commit: false); - AddStep("exit", () => Game.ChildrenOfType().Single().Exit()); + AddStep("exit", () => getEditor().Exit()); AddUntilStep("save dialog displayed", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog is PromptForSaveDialog); } @@ -121,16 +101,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestEditorGameplayTestAlwaysUsesOriginalRuleset() { - BeatmapSetInfo beatmapSet = null!; + prepareBeatmap(); - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo); AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); @@ -183,19 +155,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitEditorWithoutSelection() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + prepareBeatmap(); + openEditor(); AddStep("escape once", () => InputManager.Key(Key.Escape)); @@ -205,19 +166,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitEditorWithSelection() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + prepareBeatmap(); + openEditor(); AddStep("make selection", () => { @@ -239,19 +189,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestLastTimestampRememberedOnExit() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + prepareBeatmap(); + openEditor(); AddStep("seek to arbitrary time", () => getEditor().ChildrenOfType().First().Seek(1234)); AddUntilStep("time is correct", () => getEditor().ChildrenOfType().First().CurrentTime, () => Is.EqualTo(1234)); @@ -259,32 +198,21 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("exit editor", () => InputManager.Key(Key.Escape)); AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit()); + openEditor(); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); AddUntilStep("time is correct", () => getEditor().ChildrenOfType().First().CurrentTime, () => Is.EqualTo(1234)); } [Test] public void TestAttemptGlobalMusicOperationFromEditor() { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); + prepareBeatmap(); AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying); AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true)); AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying); - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + openEditor(); AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying); AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true)); @@ -302,20 +230,10 @@ namespace osu.Game.Tests.Visual.Navigation [TestCase(SortMode.Difficulty)] public void TestSelectionRetainedOnExit(SortMode sortMode) { - BeatmapSetInfo beatmapSet = null!; - - AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); - AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); - AddStep($"set sort mode to {sortMode}", () => Game.LocalConfig.SetValue(OsuSetting.SongSelectSortingMode, sortMode)); - AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); - AddUntilStep("wait for song select", - () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) - && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect - && songSelect.IsLoaded); - AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); - AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + prepareBeatmap(); + openEditor(); AddStep("exit editor", () => InputManager.Key(Key.Escape)); AddUntilStep("wait for editor exit", () => Game.ScreenStack.CurrentScreen is not Editor); @@ -332,6 +250,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("open editor", () => Game.ChildrenOfType().Single().OnEditBeatmap?.Invoke()); AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.IsLoaded); + AddStep("click on file", () => { var item = getEditor().ChildrenOfType().Single(i => i.Item.Text.Value.ToString() == "File"); @@ -354,6 +273,24 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("editor beatmap uses catch ruleset", () => getEditorBeatmap().BeatmapInfo.Ruleset.ShortName == "fruits"); } + private void prepareBeatmap() + { + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + } + + private void openEditor() + { + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; From aa16c72e0661b2a83337461c5b9255ff1ec0bb75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 14:51:34 +0900 Subject: [PATCH 153/670] Add test coverage of external editing --- .../TestSceneBeatmapEditorNavigation.cs | 50 +++++++++++++++++++ osu.Game/Screens/Edit/ExternalEditScreen.cs | 12 ++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index efdcde9161..5d9c3bae97 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.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.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Extensions; @@ -13,6 +14,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; @@ -31,6 +33,54 @@ namespace osu.Game.Tests.Visual.Navigation { private BeatmapSetInfo beatmapSet = null!; + [Test] + public void TestExternalEditingNoChange() + { + prepareBeatmap(); + openEditor(); + + AddStep("open file menu", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick()); + AddStep("click external edit", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick()); + + AddUntilStep("wait for external edit screen", () => Game.ScreenStack.CurrentScreen is ExternalEditScreen externalEditScreen && externalEditScreen.IsLoaded); + + AddUntilStep("wait for button ready", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().FirstOrDefault()?.Enabled.Value == true); + + AddStep("finish external edit", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().First().TriggerClick()); + + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddAssert("beatmap didn't change", () => getEditor().Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); + AddAssert("old beatmapset not deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Not.Null); + } + + [Test] + public void TestExternalEditingWithChange() + { + prepareBeatmap(); + openEditor(); + + AddStep("open file menu", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick()); + AddStep("click external edit", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick()); + + AddUntilStep("wait for external edit screen", () => Game.ScreenStack.CurrentScreen is ExternalEditScreen externalEditScreen && externalEditScreen.IsLoaded); + + AddUntilStep("wait for button ready", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().FirstOrDefault()?.Enabled.Value == true); + + AddStep("add file externally", () => + { + var op = ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).EditOperation!; + File.WriteAllText(Path.Combine(op.MountedPath, "test.txt"), "test"); + }); + + AddStep("finish external edit", () => ((ExternalEditScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType().First().TriggerClick()); + + AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddAssert("beatmap changed", () => !getEditor().Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); + AddAssert("old beatmapset deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Null); + } + [Test] public void TestSaveThenDeleteActuallyDeletesAtSongSelect() { diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index ae5fad3ec0..9cae44be78 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit private readonly Editor editor; - private ExternalEditOperation? operation; + public ExternalEditOperation? EditOperation; private double timeLoaded; @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit fileMountOperation.ContinueWith(t => { - operation = t.GetResultSafely(); + EditOperation = t.GetResultSafely(); Scheduler.AddDelayed(() => { @@ -146,11 +146,11 @@ namespace osu.Game.Screens.Edit private void open() { - if (operation == null) + if (EditOperation == null) return; // Ensure the trailing separator is present in order to show the folder contents. - gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); + gameHost.OpenFileExternally(EditOperation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); } public override bool OnExiting(ScreenExitEvent e) @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Edit if (!fileMountOperation.IsCompleted) return false; - if (operation != null) + if (EditOperation != null) { finish(); return false; @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Edit Schedule(() => { // Setting to null will allow exit to succeed. - operation = null; + EditOperation = null; Live? beatmap = t.GetResultSafely(); From 106d558147124f1e17a8d7b04226a54e2fbddc6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 18:14:54 +0900 Subject: [PATCH 154/670] Add test coverage of difficulty being retained --- .../Navigation/TestSceneBeatmapEditorNavigation.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 5d9c3bae97..1f227520c1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -36,9 +36,13 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExternalEditingNoChange() { + string difficultyName = null!; + prepareBeatmap(); openEditor(); + AddStep("store difficulty name", () => difficultyName = getEditor().Beatmap.Value.BeatmapInfo.DifficultyName); + AddStep("open file menu", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick()); AddStep("click external edit", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick()); @@ -50,16 +54,21 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddAssert("beatmap didn't change", () => getEditor().Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); + AddAssert("beatmapset didn't change", () => getEditor().Beatmap.Value.BeatmapSetInfo, () => Is.EqualTo(beatmapSet)); + AddAssert("difficulty didn't change", () => getEditor().Beatmap.Value.BeatmapInfo.DifficultyName, () => Is.EqualTo(difficultyName)); AddAssert("old beatmapset not deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Not.Null); } [Test] public void TestExternalEditingWithChange() { + string difficultyName = null!; + prepareBeatmap(); openEditor(); + AddStep("store difficulty name", () => difficultyName = getEditor().Beatmap.Value.BeatmapInfo.DifficultyName); + AddStep("open file menu", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "File").TriggerClick()); AddStep("click external edit", () => getEditor().ChildrenOfType().Single(m => m.Item.Text.Value.ToString() == "Edit externally").TriggerClick()); @@ -77,7 +86,8 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); - AddAssert("beatmap changed", () => !getEditor().Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)); + AddAssert("beatmapset changed", () => getEditor().Beatmap.Value.BeatmapSetInfo, () => Is.Not.EqualTo(beatmapSet)); + AddAssert("difficulty didn't change", () => getEditor().Beatmap.Value.BeatmapInfo.DifficultyName, () => Is.EqualTo(difficultyName)); AddAssert("old beatmapset deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Null); } From 704e7e843fc6769b391a3b455b723a9c58d335be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 18:28:11 +0900 Subject: [PATCH 155/670] More xmldoc across new methods and classes --- osu.Game/Database/ExternalEditOperation.cs | 29 +++++++++++++++++----- osu.Game/Database/IModelImporter.cs | 3 +++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/ExternalEditOperation.cs b/osu.Game/Database/ExternalEditOperation.cs index ab74cba7d5..a98d597b3c 100644 --- a/osu.Game/Database/ExternalEditOperation.cs +++ b/osu.Game/Database/ExternalEditOperation.cs @@ -7,15 +7,24 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Database { + /// + /// Contains information related to an active external edit operation. + /// public class ExternalEditOperation where TModel : class, IHasGuidPrimaryKey { + /// + /// The temporary path at which the model has been exported to for editing. + /// public readonly string MountedPath; + /// + /// Whether the model is still mounted at . + /// + public bool IsMounted { get; private set; } + private readonly IModelImporter importer; private readonly TModel original; - private bool isMounted; - public ExternalEditOperation(IModelImporter importer, TModel original, string path) { this.importer = importer; @@ -23,14 +32,24 @@ namespace osu.Game.Database MountedPath = path; - isMounted = true; + IsMounted = true; } + /// + /// Finish the external edit operation. + /// + /// + /// This will trigger an asynchronous reimport of the model. + /// Subsequent calls will be a no-op. + /// + /// A task which will eventuate in the newly imported model with changes applied. public async Task?> Finish() { - if (!Directory.Exists(MountedPath) || !isMounted) + if (!Directory.Exists(MountedPath) || !IsMounted) return null; + IsMounted = false; + Live? imported = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(MountedPath), original) .ConfigureAwait(false); @@ -40,8 +59,6 @@ namespace osu.Game.Database } catch { } - isMounted = false; - return imported; } } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index c2e5517f2a..bf19bac5dd 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -37,6 +37,9 @@ namespace osu.Game.Database /// /// Mount all files for a to a temporary directory to allow for external editing. /// + /// + /// When editing is completed, call to begin the import-and-update process. + /// /// The to mount. public Task> BeginExternalEditing(TModel model); From 343090e3b140780fd708bf69107e70bd328c0172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 10:58:29 +0200 Subject: [PATCH 156/670] Do not regenerate breaks unless meaningful change to object start/end times is detected Tangentially found when profiling https://github.com/ppy/osu/pull/28792. For reproduction, import https://osu.ppy.sh/beatmapsets/972#osu/9007, move any object on the playfield, and observe a half-second freeze when ending the drag. --- osu.Game/Screens/Edit/EditorBeatmapProcessor.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 377e978c4a..9b6d956a4c 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; @@ -18,6 +19,11 @@ namespace osu.Game.Screens.Edit private readonly IBeatmapProcessor? rulesetBeatmapProcessor; + /// + /// Kept for the purposes of reducing redundant regeneration of automatic breaks. + /// + private HashSet<(double, double)> objectDurationCache = new HashSet<(double, double)>(); + public EditorBeatmapProcessor(EditorBeatmap beatmap, Ruleset ruleset) { Beatmap = beatmap; @@ -38,6 +44,13 @@ namespace osu.Game.Screens.Edit private void autoGenerateBreaks() { + var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime, ho.GetEndTime())).ToHashSet(); + + if (objectDuration.SetEquals(objectDurationCache)) + return; + + objectDurationCache = objectDuration; + Beatmap.Breaks.RemoveAll(b => b is not ManualBreakPeriod); foreach (var manualBreak in Beatmap.Breaks.ToList()) From b881c25b17f7ae8615737432a63915c6dbbba1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 11:34:05 +0200 Subject: [PATCH 157/670] Pool summary timeline break visualisations to reduce allocations --- .../Timelines/Summary/Parts/BreakPart.cs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index ed42ade490..100f37fd27 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; @@ -17,32 +18,54 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { private readonly BindableList breaks = new BindableList(); + private DrawablePool pool = null!; + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(pool = new DrawablePool(10)); + } + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); breaks.UnbindAll(); breaks.BindTo(beatmap.Breaks); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + breaks.BindCollectionChanged((_, _) => { - Clear(); - foreach (var breakPeriod in beatmap.Breaks) - Add(new BreakVisualisation(breakPeriod)); + Clear(disposeChildren: false); + foreach (var breakPeriod in breaks) + Add(pool.Get(v => v.BreakPeriod = breakPeriod)); }, true); } - private partial class BreakVisualisation : Circle + private partial class BreakVisualisation : PoolableDrawable { - public BreakVisualisation(BreakPeriod breakPeriod) + public BreakPeriod BreakPeriod { - RelativePositionAxes = Axes.X; - RelativeSizeAxes = Axes.Both; - X = (float)breakPeriod.StartTime; - Width = (float)breakPeriod.Duration; + set + { + X = (float)value.StartTime; + Width = (float)value.Duration; + } } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Gray7; + private void load(OsuColour colours) + { + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + + InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; + Colour = colours.Gray7; + } } } } From 2ba1ebe410d088d5d05944f18a9ac6c2f4fa3ab3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 19:19:49 +0900 Subject: [PATCH 158/670] Fix beatmap card progress bar becoming pancake when starting --- .../Cards/BeatmapCardDownloadProgressBar.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index 5ea42fe4b1..d21e8e7c76 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -22,8 +22,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards public override bool IsPresent => true; - private readonly CircularContainer foreground; - private readonly Box backgroundFill; private readonly Box foregroundFill; @@ -35,22 +33,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards public BeatmapCardDownloadProgressBar() { - InternalChildren = new Drawable[] + InternalChild = new CircularContainer { - new CircularContainer + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = backgroundFill = new Box + backgroundFill = new Box { RelativeSizeAxes = Axes.Both, - } - }, - foreground = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = foregroundFill = new Box + }, + foregroundFill = new Box { RelativeSizeAxes = Axes.Both, } @@ -89,7 +82,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void progressChanged() { - foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + foregroundFill.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); } } } From 6cee0210c380e7903873ea4fa4fa29b28d417df2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 18:57:53 +0900 Subject: [PATCH 159/670] Fix(?) xmldoc --- osu.Game/Database/IModelImporter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index bf19bac5dd..ce1563f2df 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -35,12 +35,12 @@ namespace osu.Game.Database Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original); /// - /// Mount all files for a to a temporary directory to allow for external editing. + /// Mount all files for a model to a temporary directory to allow for external editing. /// /// - /// When editing is completed, call to begin the import-and-update process. + /// When editing is completed, call Finish() on the returned operation class to begin the import-and-update process. /// - /// The to mount. + /// The model to mount. public Task> BeginExternalEditing(TModel model); /// From 75344f9c5c7ad67aa82df35aa6f3d8b0a6041b74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 19:28:25 +0900 Subject: [PATCH 160/670] Fix break overlay progress bar becoming a pancake near end of break --- osu.Game/Screens/Play/BreakOverlay.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index e18612c955..ece3105b42 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -75,15 +76,13 @@ namespace osu.Game.Screens.Play AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Width = 0, - Child = remainingTimeBox = new Container + Child = remainingTimeBox = new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 8, - CornerRadius = 4, Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } } }, remainingTimeCounter = new RemainingTimeCounter @@ -119,6 +118,13 @@ namespace osu.Game.Screens.Play } } + protected override void Update() + { + base.Update(); + + remainingTimeBox.Height = Math.Min(8, remainingTimeBox.DrawWidth); + } + private void initializeBreaks() { FinishTransforms(true); From b6741ee4eab7c8bceed3fa1e94e11e60e6a7383b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 20:00:34 +0900 Subject: [PATCH 161/670] Fix back-to-front exit blocking conditionals --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index 9cae44be78..a8a75f22db 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -156,12 +156,12 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(ScreenExitEvent e) { if (!fileMountOperation.IsCompleted) - return false; + return true; if (EditOperation != null) { finish(); - return false; + return true; } return base.OnExiting(e); From 94f51c92e0d4643c3821c7d9df6804d700b1fcc9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 15:08:30 +0300 Subject: [PATCH 162/670] Select all text when focusing a number box --- .../Visual/UserInterface/TestSceneOsuTextBox.cs | 17 +++++++++++++++++ osu.Game/Graphics/UserInterface/OsuNumberBox.cs | 5 +++++ osu.Game/Graphics/UserInterface/OsuTextBox.cs | 8 ++++++++ .../Graphics/UserInterfaceV2/LabelledTextBox.cs | 6 ++++++ 4 files changed, 36 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index 69fe8ad105..921c5bbbfa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -61,6 +62,22 @@ namespace osu.Game.Tests.Visual.UserInterface clearTextboxes(numberBoxes); } + [Test] + public void TestSelectAllOnFocus() + { + AddStep("create themed content", () => CreateThemedContent(OverlayColourScheme.Red)); + + AddStep("enter numbers", () => numberBoxes.ForEach(numberBox => numberBox.Text = "987654321")); + + AddAssert("nothing selected", () => string.IsNullOrEmpty(numberBoxes.First().SelectedText)); + AddStep("click on a number box", () => + { + InputManager.MoveMouseTo(numberBoxes.First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("text selected", () => numberBoxes.First().SelectedText == "987654321"); + } + private void clearTextboxes(IEnumerable textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null)); private void expectedValue(IEnumerable textBoxes, string value) => AddAssert("expected textbox value", () => textBoxes.All(textBox => textBox.Text == value)); } diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs index e9b28f4771..db4b7b2ab3 100644 --- a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -7,6 +7,11 @@ namespace osu.Game.Graphics.UserInterface { protected override bool AllowIme => false; + public OsuNumberBox() + { + SelectAllOnFocus = true; + } + protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character); } } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 08d38837f6..90a000d441 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -63,6 +63,11 @@ namespace osu.Game.Graphics.UserInterface private Dictionary sampleMap = new Dictionary(); + /// + /// Whether all text should be selected when the gains focus. + /// + public bool SelectAllOnFocus { get; set; } + public OsuTextBox() { Height = 40; @@ -255,6 +260,9 @@ namespace osu.Game.Graphics.UserInterface BorderThickness = 3; base.OnFocus(e); + + if (SelectAllOnFocus) + SelectAll(); } protected override void OnFocusLost(FocusLostEvent e) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index fabfde4333..b2e3ff077e 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -28,6 +28,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.ReadOnly = value; } + public bool SelectAllOnFocus + { + get => Component.SelectAllOnFocus; + set => Component.SelectAllOnFocus = value; + } + public LocalisableString PlaceholderText { set => Component.PlaceholderText = value; From ce93455aa8620c7b34bf3290529e3f67c27fc7e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 15:08:36 +0300 Subject: [PATCH 163/670] Extend behaviour to sample edit popover --- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 ++ .../Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 930b78b468..1f9c7a891b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -148,10 +148,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline bank = new LabelledTextBox { Label = "Bank Name", + SelectAllOnFocus = true, }, additionBank = new LabelledTextBox { Label = "Addition Bank", + SelectAllOnFocus = true, }, volume = new IndeterminateSliderWithTextBoxInput("Volume", new BindableInt(100) { diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 01e1856e6c..00cf2e3493 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -75,6 +75,7 @@ namespace osu.Game.Screens.Edit.Timing textBox = new LabelledTextBox { Label = labelText, + SelectAllOnFocus = true, }, slider = new SettingsSlider { From c8a64c5950358af9ddfa407aa6f453cb5840f921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 14:42:11 +0200 Subject: [PATCH 164/670] Remove setup screen controls that do nothing useful Before I go with a hammer to redesign these, I want to remove stuff that does nothing first. Hard-breaks API to allow rulesets to specify an enumerable of custom sections rather than two specific weird ones. For specific rulesets: - osu!: - Stack leniency slider merged into difficulty section. - osu!taiko: - Approach rate and circle size sliders removed. - Colours section removed. - osu!catch: - No functional changes. - osu!mania: - Special style toggle merged into difficulty section. - Colours section removed. --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 7 + .../Edit/Setup/ManiaDifficultySection.cs | 9 ++ .../Edit/Setup/ManiaSetupSection.cs | 49 ------ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 7 +- .../Edit/Setup/OsuDifficultySection.cs | 150 ++++++++++++++++++ .../Edit/Setup/OsuSetupSection.cs | 56 ------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 6 +- .../Edit/Setup/TaikoDifficultySection.cs | 105 ++++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 7 + osu.Game/Rulesets/Ruleset.cs | 13 +- osu.Game/Screens/Edit/Setup/ColoursSection.cs | 2 +- .../Screens/Edit/Setup/DifficultySection.cs | 36 ++--- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 17 +- 13 files changed, 318 insertions(+), 146 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs delete mode 100644 osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ad6dedaa8f..3edc23a8b7 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -28,6 +28,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Screens.Edit.Setup; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; @@ -222,6 +223,12 @@ namespace osu.Game.Rulesets.Catch public override HitObjectComposer CreateHitObjectComposer() => new CatchHitObjectComposer(this); + public override IEnumerable CreateEditorSetupSections() => + [ + new DifficultySection(), + new ColoursSection(), + ]; + public override IBeatmapVerifier CreateBeatmapVerifier() => new CatchBeatmapVerifier(); public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs index 62b54a7215..7168504309 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaDifficultySection.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; private LabelledSliderBar keyCountSlider { get; set; } = null!; + private LabelledSwitchButton specialStyle { get; set; } = null!; private LabelledSliderBar healthDrainSlider { get; set; } = null!; private LabelledSliderBar overallDifficultySlider { get; set; } = null!; private LabelledSliderBar baseVelocitySlider { get; set; } = null!; @@ -49,6 +50,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup Precision = 1, } }, + specialStyle = new LabelledSwitchButton + { + Label = "Use special (N+1) style", + FixedLabelWidth = LABEL_WIDTH, + Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", + Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } + }, healthDrainSlider = new LabelledSliderBar { Label = BeatmapsetsStrings.ShowStatsDrain, @@ -145,6 +153,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup // for now, update these on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. Beatmap.Difficulty.CircleSize = keyCountSlider.Current.Value; + Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs deleted file mode 100644 index d5a9a311bc..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Screens.Edit.Setup; - -namespace osu.Game.Rulesets.Mania.Edit.Setup -{ - public partial class ManiaSetupSection : RulesetSetupSection - { - private LabelledSwitchButton specialStyle; - - public ManiaSetupSection() - : base(new ManiaRuleset().RulesetInfo) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - specialStyle = new LabelledSwitchButton - { - Label = "Use special (N+1) style", - Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", - Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - specialStyle.Current.BindValueChanged(_ => updateBeatmap()); - } - - private void updateBeatmap() - { - Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; - Beatmap.SaveState(); - } - } -} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 0dcbb36c77..c01fa508fe 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -419,9 +419,10 @@ namespace osu.Game.Rulesets.Mania return new ManiaFilterCriteria(); } - public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection(); - - public override SetupSection CreateEditorDifficultySection() => new ManiaDifficultySection(); + public override IEnumerable CreateEditorSetupSections() => + [ + new ManiaDifficultySection(), + ]; public int GetKeyCount(IBeatmapInfo beatmapInfo, IReadOnlyList? mods = null) => ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), mods); diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs new file mode 100644 index 0000000000..b61faa0ae9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuDifficultySection.cs @@ -0,0 +1,150 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Rulesets.Osu.Edit.Setup +{ + public partial class OsuDifficultySection : SetupSection + { + private LabelledSliderBar circleSizeSlider { get; set; } = null!; + private LabelledSliderBar healthDrainSlider { get; set; } = null!; + private LabelledSliderBar approachRateSlider { get; set; } = null!; + private LabelledSliderBar overallDifficultySlider { get; set; } = null!; + private LabelledSliderBar baseVelocitySlider { get; set; } = null!; + private LabelledSliderBar tickRateSlider { get; set; } = null!; + private LabelledSliderBar stackLeniency { get; set; } = null!; + + public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + circleSizeSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsCs, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.CircleSizeDescription, + Current = new BindableFloat(Beatmap.Difficulty.CircleSize) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + healthDrainSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsDrain, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.DrainRateDescription, + Current = new BindableFloat(Beatmap.Difficulty.DrainRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + approachRateSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsAr, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.ApproachRateDescription, + Current = new BindableFloat(Beatmap.Difficulty.ApproachRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + overallDifficultySlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsAccuracy, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.OverallDifficultyDescription, + Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + baseVelocitySlider = new LabelledSliderBar + { + Label = EditorSetupStrings.BaseVelocity, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.BaseVelocityDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) + { + Default = 1.4, + MinValue = 0.4, + MaxValue = 3.6, + Precision = 0.01f, + } + }, + tickRateSlider = new LabelledSliderBar + { + Label = EditorSetupStrings.TickRate, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.TickRateDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) + { + Default = 1, + MinValue = 1, + MaxValue = 4, + Precision = 1, + } + }, + stackLeniency = new LabelledSliderBar + { + Label = "Stack Leniency", + FixedLabelWidth = LABEL_WIDTH, + Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", + Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) + { + Default = 0.7f, + MinValue = 0, + MaxValue = 1, + Precision = 0.1f + } + }, + }; + + foreach (var item in Children.OfType>()) + item.Current.ValueChanged += _ => updateValues(); + + foreach (var item in Children.OfType>()) + item.Current.ValueChanged += _ => updateValues(); + } + + private void updateValues() + { + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value; + Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value; + Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; + Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; + Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; + + Beatmap.UpdateAllHitObjects(); + Beatmap.SaveState(); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs deleted file mode 100644 index 552b887081..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Screens.Edit.Setup; - -namespace osu.Game.Rulesets.Osu.Edit.Setup -{ - public partial class OsuSetupSection : RulesetSetupSection - { - private LabelledSliderBar stackLeniency; - - public OsuSetupSection() - : base(new OsuRuleset().RulesetInfo) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new[] - { - stackLeniency = new LabelledSliderBar - { - Label = "Stack Leniency", - Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", - Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) - { - Default = 0.7f, - MinValue = 0, - MaxValue = 1, - Precision = 0.1f - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - stackLeniency.Current.BindValueChanged(_ => updateBeatmap()); - } - - private void updateBeatmap() - { - Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; - Beatmap.UpdateAllHitObjects(); - Beatmap.SaveState(); - } - } -} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 73f9be3fdc..7042ad0cd4 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -336,7 +336,11 @@ namespace osu.Game.Rulesets.Osu }; } - public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection(); + public override IEnumerable CreateEditorSetupSections() => + [ + new OsuDifficultySection(), + new ColoursSection(), + ]; /// /// diff --git a/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs new file mode 100644 index 0000000000..2aaa16ee0b --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Setup/TaikoDifficultySection.cs @@ -0,0 +1,105 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Edit.Setup; + +namespace osu.Game.Rulesets.Taiko.Edit.Setup +{ + public partial class TaikoDifficultySection : SetupSection + { + private LabelledSliderBar healthDrainSlider { get; set; } = null!; + private LabelledSliderBar overallDifficultySlider { get; set; } = null!; + private LabelledSliderBar baseVelocitySlider { get; set; } = null!; + private LabelledSliderBar tickRateSlider { get; set; } = null!; + + public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + healthDrainSlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsDrain, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.DrainRateDescription, + Current = new BindableFloat(Beatmap.Difficulty.DrainRate) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + overallDifficultySlider = new LabelledSliderBar + { + Label = BeatmapsetsStrings.ShowStatsAccuracy, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.OverallDifficultyDescription, + Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) + { + Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, + MinValue = 0, + MaxValue = 10, + Precision = 0.1f, + } + }, + baseVelocitySlider = new LabelledSliderBar + { + Label = EditorSetupStrings.BaseVelocity, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.BaseVelocityDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier) + { + Default = 1.4, + MinValue = 0.4, + MaxValue = 3.6, + Precision = 0.01f, + } + }, + tickRateSlider = new LabelledSliderBar + { + Label = EditorSetupStrings.TickRate, + FixedLabelWidth = LABEL_WIDTH, + Description = EditorSetupStrings.TickRateDescription, + Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate) + { + Default = 1, + MinValue = 1, + MaxValue = 4, + Precision = 1, + } + }, + }; + + foreach (var item in Children.OfType>()) + item.Current.ValueChanged += _ => updateValues(); + + foreach (var item in Children.OfType>()) + item.Current.ValueChanged += _ => updateValues(); + } + + private void updateValues() + { + // for now, update these on commit rather than making BeatmapMetadata bindables. + // after switching database engines we can reconsider if switching to bindables is a good direction. + Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; + Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; + + Beatmap.UpdateAllHitObjects(); + Beatmap.SaveState(); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 2053a11426..2447a4a247 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -35,6 +35,8 @@ using osu.Game.Rulesets.Configuration; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Taiko.Configuration; +using osu.Game.Rulesets.Taiko.Edit.Setup; +using osu.Game.Screens.Edit.Setup; namespace osu.Game.Rulesets.Taiko { @@ -188,6 +190,11 @@ namespace osu.Game.Rulesets.Taiko public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); + public override IEnumerable CreateEditorSetupSections() => + [ + new TaikoDifficultySection(), + ]; + public override IBeatmapVerifier CreateBeatmapVerifier() => new TaikoBeatmapVerifier(); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new TaikoDifficultyCalculator(RulesetInfo, beatmap); diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index cae2ce610e..fb0e225c94 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -394,13 +394,12 @@ namespace osu.Game.Rulesets public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null; /// - /// Can be overridden to add a ruleset-specific section to the editor beatmap setup screen. + /// Can be overridden to add ruleset-specific sections to the editor beatmap setup screen. /// - public virtual RulesetSetupSection? CreateEditorSetupSection() => null; - - /// - /// Can be overridden to alter the difficulty section to the editor beatmap setup screen. - /// - public virtual SetupSection? CreateEditorDifficultySection() => null; + public virtual IEnumerable CreateEditorSetupSections() => + [ + new DifficultySection(), + new ColoursSection(), + ]; } } diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs index 8cd5c0f779..a5d79b5b52 100644 --- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs +++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs @@ -9,7 +9,7 @@ using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { - internal partial class ColoursSection : SetupSection + public partial class ColoursSection : SetupSection { public override LocalisableString Title => EditorSetupStrings.ColoursHeader; diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index 8028df6c0f..b9ba2d9cb7 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Setup { public partial class DifficultySection : SetupSection { - protected LabelledSliderBar CircleSizeSlider { get; private set; } = null!; - protected LabelledSliderBar HealthDrainSlider { get; private set; } = null!; - protected LabelledSliderBar ApproachRateSlider { get; private set; } = null!; - protected LabelledSliderBar OverallDifficultySlider { get; private set; } = null!; - protected LabelledSliderBar BaseVelocitySlider { get; private set; } = null!; - protected LabelledSliderBar TickRateSlider { get; private set; } = null!; + private LabelledSliderBar circleSizeSlider { get; set; } = null!; + private LabelledSliderBar healthDrainSlider { get; set; } = null!; + private LabelledSliderBar approachRateSlider { get; set; } = null!; + private LabelledSliderBar overallDifficultySlider { get; set; } = null!; + private LabelledSliderBar baseVelocitySlider { get; set; } = null!; + private LabelledSliderBar tickRateSlider { get; set; } = null!; public override LocalisableString Title => EditorSetupStrings.DifficultyHeader; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit.Setup { Children = new Drawable[] { - CircleSizeSlider = new LabelledSliderBar + circleSizeSlider = new LabelledSliderBar { Label = BeatmapsetsStrings.ShowStatsCs, FixedLabelWidth = LABEL_WIDTH, @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Setup Precision = 0.1f, } }, - HealthDrainSlider = new LabelledSliderBar + healthDrainSlider = new LabelledSliderBar { Label = BeatmapsetsStrings.ShowStatsDrain, FixedLabelWidth = LABEL_WIDTH, @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup Precision = 0.1f, } }, - ApproachRateSlider = new LabelledSliderBar + approachRateSlider = new LabelledSliderBar { Label = BeatmapsetsStrings.ShowStatsAr, FixedLabelWidth = LABEL_WIDTH, @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Setup Precision = 0.1f, } }, - OverallDifficultySlider = new LabelledSliderBar + overallDifficultySlider = new LabelledSliderBar { Label = BeatmapsetsStrings.ShowStatsAccuracy, FixedLabelWidth = LABEL_WIDTH, @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Setup Precision = 0.1f, } }, - BaseVelocitySlider = new LabelledSliderBar + baseVelocitySlider = new LabelledSliderBar { Label = EditorSetupStrings.BaseVelocity, FixedLabelWidth = LABEL_WIDTH, @@ -94,7 +94,7 @@ namespace osu.Game.Screens.Edit.Setup Precision = 0.01f, } }, - TickRateSlider = new LabelledSliderBar + tickRateSlider = new LabelledSliderBar { Label = EditorSetupStrings.TickRate, FixedLabelWidth = LABEL_WIDTH, @@ -120,12 +120,12 @@ namespace osu.Game.Screens.Edit.Setup { // for now, update these on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. - Beatmap.Difficulty.CircleSize = CircleSizeSlider.Current.Value; - Beatmap.Difficulty.DrainRate = HealthDrainSlider.Current.Value; - Beatmap.Difficulty.ApproachRate = ApproachRateSlider.Current.Value; - Beatmap.Difficulty.OverallDifficulty = OverallDifficultySlider.Current.Value; - Beatmap.Difficulty.SliderMultiplier = BaseVelocitySlider.Current.Value; - Beatmap.Difficulty.SliderTickRate = TickRateSlider.Current.Value; + Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value; + Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value; + Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + Beatmap.Difficulty.SliderMultiplier = baseVelocitySlider.Current.Value; + Beatmap.Difficulty.SliderTickRate = tickRateSlider.Current.Value; Beatmap.UpdateAllHitObjects(); Beatmap.SaveState(); diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 7a7907d08a..6eba678245 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -29,18 +29,13 @@ namespace osu.Game.Screens.Edit.Setup { var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance(); - var sectionsEnumerable = new List - { - new ResourcesSection(), - new MetadataSection(), - ruleset.CreateEditorDifficultySection() ?? new DifficultySection(), - new ColoursSection(), - new DesignSection(), - }; + // ReSharper disable once UseObjectOrCollectionInitializer + var sectionsEnumerable = new List(); - var rulesetSpecificSection = ruleset.CreateEditorSetupSection(); - if (rulesetSpecificSection != null) - sectionsEnumerable.Add(rulesetSpecificSection); + sectionsEnumerable.Add(new ResourcesSection()); + sectionsEnumerable.Add(new MetadataSection()); + sectionsEnumerable.AddRange(ruleset.CreateEditorSetupSections()); + sectionsEnumerable.Add(new DesignSection()); Add(new Box { From 7d667ac46bcae641a51c0aee9995bd2a6ae44e48 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:01:45 +0300 Subject: [PATCH 165/670] Fix confirm exit dialog overflowing from too many ongoing operations --- .../Navigation/TestSceneScreenNavigation.cs | 23 ++++++++++--------- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 8 ++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 0fa2fd4b0b..88235d58d3 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -49,6 +49,7 @@ using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; +using SharpCompress; namespace osu.Game.Tests.Visual.Navigation { @@ -839,18 +840,15 @@ namespace osu.Game.Tests.Visual.Navigation { AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); - ProgressNotification progressNotification = null!; - - AddStep("start ongoing operation", () => + AddRepeatStep("start ongoing operation", () => { - progressNotification = new ProgressNotification + Game.Notifications.Post(new ProgressNotification { Text = "Something is still running", Progress = 0.5f, State = ProgressNotificationState.Active, - }; - Game.Notifications.Post(progressNotification); - }); + }); + }, 15); AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); @@ -861,8 +859,11 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("complete operation", () => { - progressNotification.Progress = 100; - progressNotification.State = ProgressNotificationState.Completed; + this.ChildrenOfType().ForEach(n => + { + n.Progress = 100; + n.State = ProgressNotificationState.Completed; + }); }); AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); @@ -878,7 +879,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0)); AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); - AddStep("start ongoing operation", () => + AddRepeatStep("start ongoing operation", () => { Game.Notifications.Post(new ProgressNotification { @@ -886,7 +887,7 @@ namespace osu.Game.Tests.Visual.Navigation Progress = 0.5f, State = ProgressNotificationState.Active, }); - }); + }, 15); AddRepeatStep("attempt force exit", () => Game.ScreenStack.CurrentScreen.Exit(), 2); AddUntilStep("stopped at exit confirm", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 0041d047bd..9243f2be54 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Game.Localisation; @@ -37,9 +38,14 @@ namespace osu.Game.Screens.Menu { string text = "There are currently some background operations which will be aborted if you continue:\n\n"; - foreach (var n in notifications.OngoingOperations) + var ongoingOperations = notifications.OngoingOperations.ToArray(); + + foreach (var n in ongoingOperations.Take(Math.Min(ongoingOperations.Length, 10))) text += $"{n.Text} ({n.Progress:0%})\n"; + if (ongoingOperations.Length > 10) + text += $"\nAnd {ongoingOperations.Length - 10} other operation(s).\n"; + text += "\nLast chance to turn back"; BodyText = text; From fa749d317e0bb24a5591143023713ae2aa1d6f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 15:12:03 +0200 Subject: [PATCH 166/670] Enable NRT on `ManiaHitObjectComposer` --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index c229039dc3..7a197f9d6f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public partial class ManiaHitObjectComposer : ScrollingHitObjectComposer { - private DrawableManiaEditorRuleset drawableRuleset; + private DrawableManiaEditorRuleset drawableRuleset = null!; public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -72,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Edit if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column)) continue; - ManiaHitObject current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column); + ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column); if (current == null) continue; From 8ca8648a0916099b85fb034e472cbf630d31845d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:14:12 +0300 Subject: [PATCH 167/670] Add failing test case --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 88235d58d3..e81c6d2e86 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -838,18 +838,25 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestExitWithOperationInProgress() { - AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + int x = 0; + + AddUntilStep("wait for dialog overlay", () => + { + x = 0; + return Game.ChildrenOfType().SingleOrDefault() != null; + }); AddRepeatStep("start ongoing operation", () => { Game.Notifications.Post(new ProgressNotification { - Text = "Something is still running", + Text = $"Something is still running #{++x}", Progress = 0.5f, State = ProgressNotificationState.Active, }); }, 15); + AddAssert("all notifications = 15", () => Game.Notifications.AllNotifications.Count(), () => Is.EqualTo(15)); AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); AddUntilStep("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is ConfirmExitDialog); AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); From 846fd73ac9c55405d4605c6968f82b78f79bb488 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:15:02 +0300 Subject: [PATCH 168/670] Fix notification toast tray potentially hiding some notifications --- .../Overlays/NotificationOverlayToastTray.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index 0ebaff9437..e019b31620 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,16 +24,16 @@ namespace osu.Game.Overlays /// public partial class NotificationOverlayToastTray : CompositeDrawable { - public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; + public override bool IsPresent => toastContentBackground.Height > 0 || Notifications.Any(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); /// /// All notifications currently being displayed by the toast tray. /// - public IEnumerable Notifications => toastFlow; + public IEnumerable Notifications => toastFlow.Concat(InternalChildren.OfType()); - public bool IsDisplayingToasts => toastFlow.Count > 0; + public bool IsDisplayingToasts => Notifications.Any(); private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; @@ -43,12 +43,7 @@ namespace osu.Game.Overlays public Action? ForwardNotificationToPermanentStore { get; set; } - public int UnreadCount => allDisplayedNotifications.Count(n => !n.WasClosed && !n.Read); - - /// - /// Notifications contained in the toast flow, or in a detached state while they animate during forwarding to the main overlay. - /// - private IEnumerable allDisplayedNotifications => toastFlow.Concat(InternalChildren.OfType()); + public int UnreadCount => Notifications.Count(n => !n.WasClosed && !n.Read); private int runningDepth; @@ -91,11 +86,7 @@ namespace osu.Game.Overlays }; } - public void MarkAllRead() - { - toastFlow.Children.ForEach(n => n.Read = true); - InternalChildren.OfType().ForEach(n => n.Read = true); - } + public void MarkAllRead() => Notifications.ForEach(n => n.Read = true); public void FlushAllToasts() { From bb9a2b705e576bead9e6102d3da75f0940304ee0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jul 2024 16:29:28 +0300 Subject: [PATCH 169/670] Remove unnecessary math min --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 9243f2be54..1e444a896b 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Menu var ongoingOperations = notifications.OngoingOperations.ToArray(); - foreach (var n in ongoingOperations.Take(Math.Min(ongoingOperations.Length, 10))) + foreach (var n in ongoingOperations.Take(10)) text += $"{n.Text} ({n.Progress:0%})\n"; if (ongoingOperations.Length > 10) From b58ba5f5f1fd7815d0754efb6651f62e9dee4418 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jul 2024 23:02:19 +0900 Subject: [PATCH 170/670] Just give in to silly code quality inspection --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 6eba678245..17bbc7daa2 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -29,11 +29,12 @@ namespace osu.Game.Screens.Edit.Setup { var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance(); - // ReSharper disable once UseObjectOrCollectionInitializer - var sectionsEnumerable = new List(); + List sectionsEnumerable = + [ + new ResourcesSection(), + new MetadataSection() + ]; - sectionsEnumerable.Add(new ResourcesSection()); - sectionsEnumerable.Add(new MetadataSection()); sectionsEnumerable.AddRange(ruleset.CreateEditorSetupSections()); sectionsEnumerable.Add(new DesignSection()); From 92dc125d391fa5bfd0420a1a220a2417b2dd6b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 17:24:03 +0200 Subject: [PATCH 171/670] Match mania editor playfield time range with timeline zoom --- .../Edit/DrawableManiaEditorRuleset.cs | 9 +++++++++ .../Edit/ManiaHitObjectComposer.cs | 13 +++++++++++++ .../UI/DrawableManiaRuleset.cs | 18 +++++++++++++----- .../Screens/Edit/EditorScreenWithTimeline.cs | 9 ++++----- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs index 8d34373f82..4c4cf519ce 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditorRuleset.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -18,6 +19,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public BindableBool ShowSpeedChanges { get; } = new BindableBool(); + public double? TimelineTimeRange { get; set; } + public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; public DrawableManiaEditorRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods) @@ -38,5 +41,11 @@ namespace osu.Game.Rulesets.Mania.Edit Origin = Anchor.Centre, Size = Vector2.One }; + + protected override void Update() + { + TargetTimeRange = TimelineTimeRange == null || ShowSpeedChanges.Value ? ComputeScrollTime(Config.Get(ManiaRulesetSetting.ScrollSpeed)) : TimelineTimeRange.Value; + base.Update(); + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 7a197f9d6f..02a4f3a022 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -12,6 +13,7 @@ using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -21,6 +23,9 @@ namespace osu.Game.Rulesets.Mania.Edit { private DrawableManiaEditorRuleset drawableRuleset = null!; + [Resolved] + private EditorScreenWithTimeline? screenWithTimeline { get; set; } + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -81,5 +86,13 @@ namespace osu.Game.Rulesets.Mania.Edit remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList(); } } + + protected override void Update() + { + base.Update(); + + if (screenWithTimeline?.TimelineArea.Timeline != null) + drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2; + } } } diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index ce53862c76..aed53e157a 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -8,9 +8,10 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; -using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; @@ -56,13 +57,18 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); private readonly BindableInt configScrollSpeed = new BindableInt(); - private double smoothTimeRange; + + private double currentTimeRange; + protected double TargetTimeRange; // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); private ISkinSource currentSkin = null!; + [Resolved] + private GameHost gameHost { get; set; } = null!; + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { @@ -101,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.UI configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed); - configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint)); + configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue)); - TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value); + TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value); KeyBindingInputManager.Add(new ManiaTouchInputArea()); } @@ -144,7 +150,9 @@ namespace osu.Game.Rulesets.Mania.UI // This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position. float scale = lengthToHitPosition / length_to_default_hit_position; - TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale; + // we're intentionally using the game host's update clock here to decouple the time range tween from the gameplay clock (which can be arbitrarily paused, or even rewinding) + currentTimeRange = Interpolation.DampContinuously(currentTimeRange, TargetTimeRange, 50, gameHost.UpdateThread.Clock.ElapsedFrameTime); + TimeRange.Value = currentTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale; } /// diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index cdc8a26c35..38d2a1e7e4 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -13,13 +13,12 @@ namespace osu.Game.Screens.Edit [Cached] public abstract partial class EditorScreenWithTimeline : EditorScreen { - public const float PADDING = 10; - - public Container TimelineContent { get; private set; } = null!; + public TimelineArea TimelineArea { get; private set; } = null!; public Container MainContent { get; private set; } = null!; private LoadingSpinner spinner = null!; + private Container timelineContent = null!; protected EditorScreenWithTimeline(EditorScreenMode type) : base(type) @@ -60,7 +59,7 @@ namespace osu.Game.Screens.Edit { new Drawable[] { - TimelineContent = new Container + timelineContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -108,7 +107,7 @@ namespace osu.Game.Screens.Edit MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(new TimelineArea(CreateTimelineContent()), TimelineContent.Add); + LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add); }); } From 55b4dd9b99bbe637d90e7e9ebc2b8ff36b10952d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 10 Jul 2024 18:17:10 +0200 Subject: [PATCH 172/670] Adjust punctuation --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 1e444a896b..e33071e78c 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu text += $"{n.Text} ({n.Progress:0%})\n"; if (ongoingOperations.Length > 10) - text += $"\nAnd {ongoingOperations.Length - 10} other operation(s).\n"; + text += $"\nand {ongoingOperations.Length - 10} other operation(s).\n"; text += "\nLast chance to turn back"; From b0d6c8ca6d59e3ced2684eafcc9d66135abe5b76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 11:42:06 +0900 Subject: [PATCH 173/670] Abort operation on save failure --- osu.Game/Screens/Edit/Editor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a675b41833..8585aa910f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1135,7 +1135,8 @@ namespace osu.Game.Screens.Edit private void editExternally() { - Save(); + if (!Save()) + return; var editOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); this.Push(new ExternalEditScreen(editOperation, this)); From cd6b0e875a90ddc0fa5423c57afd3fcad4038d67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 12:15:17 +0900 Subject: [PATCH 174/670] Simplify save dialogs --- osu.Game/Screens/Edit/Editor.cs | 19 ++++++++----------- .../Screens/Edit/SaveRequiredPopupDialog.cs | 4 ++-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8585aa910f..700f355207 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -481,7 +481,7 @@ namespace osu.Game.Screens.Edit { if (HasUnsavedChanges) { - dialogOverlay.Push(new SaveRequiredPopupDialog("The beatmap will be saved in order to test it.", () => attemptMutationOperation(() => + dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptMutationOperation(() => { if (!Save()) return false; @@ -1146,7 +1146,7 @@ namespace osu.Game.Screens.Edit { if (HasUnsavedChanges) { - dialogOverlay.Push(new SaveRequiredPopupDialog("The beatmap will be saved in order to export it.", () => attemptAsyncMutationOperation(() => + dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptAsyncMutationOperation(() => { if (!Save()) return Task.CompletedTask; @@ -1224,17 +1224,14 @@ namespace osu.Game.Screens.Edit { if (isNewBeatmap) { - dialogOverlay.Push(new SaveRequiredPopupDialog("This beatmap will be saved in order to create another difficulty.", () => + dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptMutationOperation(() => { - attemptMutationOperation(() => - { - if (!Save()) - return false; + if (!Save()) + return false; - CreateNewDifficulty(rulesetInfo); - return true; - }); - })); + CreateNewDifficulty(rulesetInfo); + return true; + }))); return; } diff --git a/osu.Game/Screens/Edit/SaveRequiredPopupDialog.cs b/osu.Game/Screens/Edit/SaveRequiredPopupDialog.cs index 3ca92876f1..618efb7cda 100644 --- a/osu.Game/Screens/Edit/SaveRequiredPopupDialog.cs +++ b/osu.Game/Screens/Edit/SaveRequiredPopupDialog.cs @@ -9,9 +9,9 @@ namespace osu.Game.Screens.Edit { public partial class SaveRequiredPopupDialog : PopupDialog { - public SaveRequiredPopupDialog(string headerText, Action saveAndAction) + public SaveRequiredPopupDialog(Action saveAndAction) { - HeaderText = headerText; + HeaderText = "The beatmap will be saved to continue with this operation."; Icon = FontAwesome.Regular.Save; From 599a765fd18f70e96e23da40462b689e7eca0e66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 12:20:33 +0900 Subject: [PATCH 175/670] Add confirmation before saving for external edit --- osu.Game/Screens/Edit/Editor.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 700f355207..7115147d0b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1135,11 +1135,27 @@ namespace osu.Game.Screens.Edit private void editExternally() { - if (!Save()) - return; + if (HasUnsavedChanges) + { + dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptMutationOperation(() => + { + if (!Save()) + return false; - var editOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); - this.Push(new ExternalEditScreen(editOperation, this)); + startEdit(); + return true; + }))); + } + else + { + startEdit(); + } + + void startEdit() + { + var editOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); + this.Push(new ExternalEditScreen(editOperation, this)); + } } private void exportBeatmap(bool legacy) From 2eb6cf57afd0b15ac19352283dbe468e8e9b09a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 13:42:24 +0900 Subject: [PATCH 176/670] Fix incorrect continuation in `ImportAsUpdate` causing UI blockage --- osu.Game/Beatmaps/BeatmapImporter.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 2137f33e77..71aa5b0333 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -43,7 +43,9 @@ namespace osu.Game.Beatmaps public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { - var imported = await Import(notification, new[] { importTask }).ConfigureAwait(true); + Guid originalId = original.ID; + + var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false); if (!imported.Any()) return null; @@ -53,7 +55,7 @@ namespace osu.Game.Beatmaps var first = imported.First(); // If there were no changes, ensure we don't accidentally nuke ourselves. - if (first.ID == original.ID) + if (first.ID == originalId) { first.PerformRead(s => { @@ -69,7 +71,8 @@ namespace osu.Game.Beatmaps Logger.Log($"Beatmap \"{updated}\" update completed successfully", LoggingTarget.Database); - original = realm!.Find(original.ID)!; + // Re-fetch as we are likely on a different thread. + original = realm!.Find(originalId)!; // Generally the import process will do this for us if the OnlineIDs match, // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). From bdbdc3592ee8deb9aaeccc23d8bcf13fcfefbe12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 14:27:12 +0900 Subject: [PATCH 177/670] Move full export async flow inside screen and add error handling --- osu.Game/Screens/Edit/Editor.cs | 3 +- osu.Game/Screens/Edit/ExternalEditScreen.cs | 218 +++++++++++--------- 2 files changed, 118 insertions(+), 103 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7115147d0b..d841e68263 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1153,8 +1153,7 @@ namespace osu.Game.Screens.Edit void startEdit() { - var editOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!); - this.Push(new ExternalEditScreen(editOperation, this)); + this.Push(new ExternalEditScreen()); } } diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index a8a75f22db..ef497020f8 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.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 System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -20,6 +19,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; @@ -28,28 +28,27 @@ namespace osu.Game.Screens.Edit { internal partial class ExternalEditScreen : OsuScreen { - private readonly Task> fileMountOperation; - [Resolved] private GameHost gameHost { get; set; } = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - private readonly Editor editor; + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; + + [Resolved] + private Editor editor { get; set; } = null!; + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + private Task? fileMountOperation; public ExternalEditOperation? EditOperation; - private double timeLoaded; - private FillFlowContainer flow = null!; - public ExternalEditScreen(Task> fileMountOperation, Editor editor) - { - this.fileMountOperation = fileMountOperation; - this.editor = editor; - } - [BackgroundDependencyLoader] private void load() { @@ -80,71 +79,98 @@ namespace osu.Game.Screens.Edit } } }; - - showSpinner("Exporting for edit..."); } protected override void LoadComplete() { base.LoadComplete(); - timeLoaded = Time.Current; - - fileMountOperation.ContinueWith(t => - { - EditOperation = t.GetResultSafely(); - - Scheduler.AddDelayed(() => - { - flow.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Beatmap is mounted externally", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, - new OsuTextFlowContainer - { - Padding = new MarginPadding(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 350, - AutoSizeAxes = Axes.Y, - Text = "Any changes made to the exported folder will be imported to the game, including file additions, modifications and deletions.", - }, - new PurpleRoundedButton - { - Text = "Open folder", - Width = 350, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Action = open, - Enabled = { Value = false } - }, - new DangerousRoundedButton - { - Text = "Finish editing and import changes", - Width = 350, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Action = finish, - Enabled = { Value = false } - } - }; - - Scheduler.AddDelayed(() => - { - foreach (var b in flow.ChildrenOfType()) - b.Enabled.Value = true; - open(); - }, 1000); - }, Math.Max(0, 1000 - (Time.Current - timeLoaded))); - }); + fileMountOperation = begin(); } - private void open() + public override bool OnExiting(ScreenExitEvent e) + { + // Don't allow exiting until the file mount operation has completed. + // This is mainly to simplify the flow (once the screen is pushed we are guaranteed an attempted mount). + if (fileMountOperation?.IsCompleted == false) + return true; + + // If the operation completed successfully, ensure that we finish the operation before exiting. + // The finish() call will subsequently call Exit() when done. + if (EditOperation != null) + { + finish().FireAndForget(); + return true; + } + + return base.OnExiting(e); + } + + private async Task begin() + { + showSpinner("Exporting for edit..."); + + await Task.Delay(500).ConfigureAwait(true); + + try + { + EditOperation = await beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!).ConfigureAwait(true); + } + catch + { + fileMountOperation = null; + showSpinner("Export failed!"); + await Task.Delay(1000).ConfigureAwait(true); + this.Exit(); + } + + flow.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Beatmap is mounted externally", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new OsuTextFlowContainer + { + Padding = new MarginPadding(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 350, + AutoSizeAxes = Axes.Y, + Text = "Any changes made to the exported folder will be imported to the game, including file additions, modifications and deletions.", + }, + new PurpleRoundedButton + { + Text = "Open folder", + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = openDirectory, + Enabled = { Value = false } + }, + new DangerousRoundedButton + { + Text = "Finish editing and import changes", + Width = 350, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Action = () => finish().FireAndForget(), + Enabled = { Value = false } + } + }; + + Scheduler.AddDelayed(() => + { + foreach (var b in flow.ChildrenOfType()) + b.Enabled.Value = true; + openDirectory(); + }, 1000); + } + + private void openDirectory() { if (EditOperation == null) return; @@ -153,47 +179,37 @@ namespace osu.Game.Screens.Edit gameHost.OpenFileExternally(EditOperation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar); } - public override bool OnExiting(ScreenExitEvent e) - { - if (!fileMountOperation.IsCompleted) - return true; - - if (EditOperation != null) - { - finish(); - return true; - } - - return base.OnExiting(e); - } - - private void finish() + private async Task finish() { string originalDifficulty = editor.Beatmap.Value.Beatmap.BeatmapInfo.DifficultyName; showSpinner("Cleaning up..."); - EditOperation!.Finish().ContinueWith(t => + Live? beatmap = null; + + try { - Schedule(() => - { - // Setting to null will allow exit to succeed. - EditOperation = null; + beatmap = await EditOperation!.Finish().ConfigureAwait(true); + } + catch + { + showSpinner("Import failed!"); + await Task.Delay(1000).ConfigureAwait(true); + } - Live? beatmap = t.GetResultSafely(); + // Setting to null will allow exit to succeed. + EditOperation = null; - if (beatmap == null) - this.Exit(); - else - { - var closestMatchingBeatmap = - beatmap.Value.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalDifficulty) - ?? beatmap.Value.Beatmaps.First(); + if (beatmap == null) + this.Exit(); + else + { + var closestMatchingBeatmap = + beatmap.Value.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalDifficulty) + ?? beatmap.Value.Beatmaps.First(); - editor.SwitchToDifficulty(closestMatchingBeatmap); - } - }); - }); + editor.SwitchToDifficulty(closestMatchingBeatmap); + } } private void showSpinner(string text) From a9c8c6e74d5e1524c69648d0b49ad2efbe10b4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 08:18:16 +0200 Subject: [PATCH 178/670] Attempt to fix test failures by forcing refresh --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index d30b3c089e..16e66cb2c5 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -168,12 +168,12 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + // should only contain the modified beatmap (others purged). Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1)); Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); - realm.Run(r => r.Refresh()); - checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); From fe421edd8f2b86d1097c650cb89771af67be8f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 16:41:21 +0900 Subject: [PATCH 179/670] Fix editor UI transparency being incorrectly opaque when hovering slider control points As mentioned at https://github.com/ppy/osu/pull/28787#issuecomment-2221150025. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 30 +++---------------- osu.Game/Screens/Edit/BottomBar.cs | 1 + .../Components/Timeline/TimelineArea.cs | 1 + osu.Game/Screens/Edit/Editor.cs | 3 ++ 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1ba488d027..3c38a7258e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Edit InternalChildren = new[] { - PlayfieldContentContainer = new ContentContainer + PlayfieldContentContainer = new Container { Name = "Playfield content", RelativeSizeAxes = Axes.Y, @@ -269,6 +269,7 @@ namespace osu.Game.Rulesets.Edit composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. if (!composerFocusMode.Value) { leftToolboxBackground.FadeIn(750, Easing.OutQuint); @@ -303,6 +304,8 @@ namespace osu.Game.Rulesets.Edit PlayfieldContentContainer.Width = Math.Max(1024, DrawWidth) - (TOOLBOX_CONTRACTED_SIZE_LEFT + TOOLBOX_CONTRACTED_SIZE_RIGHT); PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT; } + + composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position); } public override Playfield Playfield => drawableRulesetWrapper.Playfield; @@ -529,31 +532,6 @@ namespace osu.Game.Rulesets.Edit } #endregion - - private partial class ContentContainer : Container - { - public override bool HandlePositionalInput => true; - - private readonly Bindable composerFocusMode = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load([CanBeNull] Editor editor) - { - if (editor != null) - composerFocusMode.BindTo(editor.ComposerFocusMode); - } - - protected override bool OnHover(HoverEvent e) - { - composerFocusMode.Value = true; - return false; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - composerFocusMode.Value = false; - } - } } /// diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index dd56752119..680f61ceaa 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -82,6 +82,7 @@ namespace osu.Game.Screens.Edit saveInProgress.BindValueChanged(_ => TestGameplayButton.Enabled.Value = !saveInProgress.Value, true); composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. foreach (var c in this.ChildrenOfType()) { if (!composerFocusMode.Value) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index ff92e658d9..1db067c846 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -134,6 +134,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline composerFocusMode.BindValueChanged(_ => { + // Transforms should be kept in sync with other usages of composer focus mode. if (!composerFocusMode.Value) timelineBackground.FadeIn(750, Easing.OutQuint); else diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 27d0392b1e..db94b08406 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -218,6 +218,9 @@ namespace osu.Game.Screens.Edit /// This controls the opacity of components like the timelines, sidebars, etc. /// In "composer focus" mode the opacity of the aforementioned components is reduced so that the user can focus on the composer better. /// + /// + /// The state of this bindable is controlled by . + /// public Bindable ComposerFocusMode { get; } = new Bindable(); public Editor(EditorLoader loader = null) From a859978efddc9e9e4761078fc8daa65ee5cb0b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 09:43:00 +0200 Subject: [PATCH 180/670] Add failing test steps for locally modified state not being set --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 1f227520c1..b5dfa9a87f 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -87,6 +87,8 @@ namespace osu.Game.Tests.Visual.Navigation AddUntilStep("wait for editor", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); AddAssert("beatmapset changed", () => getEditor().Beatmap.Value.BeatmapSetInfo, () => Is.Not.EqualTo(beatmapSet)); + AddAssert("beatmapset is locally modified", () => getEditor().Beatmap.Value.BeatmapSetInfo.Status, () => Is.EqualTo(BeatmapOnlineStatus.LocallyModified)); + AddAssert("all difficulties are locally modified", () => getEditor().Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Status == BeatmapOnlineStatus.LocallyModified)); AddAssert("difficulty didn't change", () => getEditor().Beatmap.Value.BeatmapInfo.DifficultyName, () => Is.EqualTo(difficultyName)); AddAssert("old beatmapset deleted", () => Game.BeatmapManager.QueryBeatmapSet(s => s.ID == beatmapSet.ID), () => Is.Null); } From ac467cf73a2bcd1af6c7e709227b7e5f77180fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 09:43:20 +0200 Subject: [PATCH 181/670] Set locally modified state for all externally modified beatmap(sets) that could not be mapped to online --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index ef497020f8..edfaa59e30 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -204,6 +204,16 @@ namespace osu.Game.Screens.Edit this.Exit(); else { + // the `ImportAsUpdate()` flow will yield beatmap(sets) with online status of `None` if online lookup fails. + // coerce such models to `LocallyModified` state instead to unify behaviour with normal editing flow. + beatmap.PerformWrite(s => + { + if (s.Status == BeatmapOnlineStatus.None) + s.Status = BeatmapOnlineStatus.LocallyModified; + foreach (var difficulty in s.Beatmaps.Where(b => b.Status == BeatmapOnlineStatus.None)) + difficulty.Status = BeatmapOnlineStatus.LocallyModified; + }); + var closestMatchingBeatmap = beatmap.Value.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalDifficulty) ?? beatmap.Value.Beatmaps.First(); From cc0d7e99814e70068bf283583efeff864c80834d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 09:54:12 +0200 Subject: [PATCH 182/670] Add error logging on failure to begin/end external edit --- osu.Game/Screens/Edit/ExternalEditScreen.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/ExternalEditScreen.cs b/osu.Game/Screens/Edit/ExternalEditScreen.cs index edfaa59e30..8a97e3dcb2 100644 --- a/osu.Game/Screens/Edit/ExternalEditScreen.cs +++ b/osu.Game/Screens/Edit/ExternalEditScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -116,8 +118,9 @@ namespace osu.Game.Screens.Edit { EditOperation = await beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!).ConfigureAwait(true); } - catch + catch (Exception ex) { + Logger.Log($@"Failed to initiate external edit operation: {ex}", LoggingTarget.Database); fileMountOperation = null; showSpinner("Export failed!"); await Task.Delay(1000).ConfigureAwait(true); @@ -191,8 +194,9 @@ namespace osu.Game.Screens.Edit { beatmap = await EditOperation!.Finish().ConfigureAwait(true); } - catch + catch (Exception ex) { + Logger.Log($@"Failed to finish external edit operation: {ex}", LoggingTarget.Database); showSpinner("Import failed!"); await Task.Delay(1000).ConfigureAwait(true); } From 7b0c1e34989f8f7c1c90d0306db20061aa331e6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Jul 2024 16:54:27 +0900 Subject: [PATCH 183/670] Also apply to timing textboxes --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 3 ++- osu.Game/Screens/Edit/Timing/TimingSection.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 487a871881..d715c3ebc9 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -51,7 +51,8 @@ namespace osu.Game.Screens.Edit.Timing { textBox = new LabelledTextBox { - Label = "Time" + Label = "Time", + SelectAllOnFocus = true, }, button = new RoundedButton { diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 2757753b07..838eb1f9fd 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -79,6 +79,7 @@ namespace osu.Game.Screens.Edit.Timing public BPMTextBox() { Label = "BPM"; + SelectAllOnFocus = true; OnCommit += (_, isNew) => { From 6801ccbbc51da07312bb228aa64c0eb87ff70d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 11:23:09 +0200 Subject: [PATCH 184/670] Fix editor UI remaining transparent when switching away from compose tab Could still happen if using the keyboard F-key shortcuts. In that case the composer becomes non-present, so its `Update()` can't really do anything. --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index db94b08406..6be8dafa66 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Edit /// In "composer focus" mode the opacity of the aforementioned components is reduced so that the user can focus on the composer better. /// /// - /// The state of this bindable is controlled by . + /// The state of this bindable is controlled by when in mode. /// public Bindable ComposerFocusMode { get; } = new Bindable(); @@ -1018,6 +1018,9 @@ namespace osu.Game.Screens.Edit } finally { + if (Mode.Value != EditorScreenMode.Compose) + ComposerFocusMode.Value = false; + updateSampleDisabledState(); rebindClipboardBindables(); } From 7b541d378c5cb896ee0dc3bbdf2ec377b87587e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:22:23 +0300 Subject: [PATCH 185/670] Revert some changes I can see `IsDisplayingToast` being removed and `IsPresent` becoming `=> Notifications.Any()` but I'll just leave this for another day. --- osu.Game/Overlays/NotificationOverlayToastTray.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlayToastTray.cs b/osu.Game/Overlays/NotificationOverlayToastTray.cs index e019b31620..d2899f29b8 100644 --- a/osu.Game/Overlays/NotificationOverlayToastTray.cs +++ b/osu.Game/Overlays/NotificationOverlayToastTray.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays /// public partial class NotificationOverlayToastTray : CompositeDrawable { - public override bool IsPresent => toastContentBackground.Height > 0 || Notifications.Any(); + public override bool IsPresent => toastContentBackground.Height > 0 || toastFlow.Count > 0; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => toastFlow.ReceivePositionalInputAt(screenSpacePos); @@ -33,7 +33,7 @@ namespace osu.Game.Overlays /// public IEnumerable Notifications => toastFlow.Concat(InternalChildren.OfType()); - public bool IsDisplayingToasts => Notifications.Any(); + public bool IsDisplayingToasts => toastFlow.Count > 0; private FillFlowContainer toastFlow = null!; private BufferedContainer toastContentBackground = null!; From 3190f8bb7e69e28dcef6cccfa260ca8f6483c910 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:29:30 +0300 Subject: [PATCH 186/670] Remove key arrow handling in `VolumeOverlay` --- osu.Game/Overlays/VolumeOverlay.cs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 5470c70400..9747a543fc 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -179,30 +179,6 @@ namespace osu.Game.Overlays return base.OnMouseMove(e); } - protected override bool OnKeyDown(KeyDownEvent e) - { - switch (e.Key) - { - case Key.Left: - Adjust(GlobalAction.PreviousVolumeMeter); - return true; - - case Key.Right: - Adjust(GlobalAction.NextVolumeMeter); - return true; - - case Key.Down: - Adjust(GlobalAction.DecreaseVolume); - return true; - - case Key.Up: - Adjust(GlobalAction.IncreaseVolume); - return true; - } - - return base.OnKeyDown(e); - } - protected override bool OnHover(HoverEvent e) { schedulePopOut(); From bd44c17079764184e9ebd591825ed536221b0acf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:29:56 +0300 Subject: [PATCH 187/670] Enable NRT in `VolumeOverlay` --- osu.Game/Overlays/VolumeOverlay.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 9747a543fc..6f9861c703 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -19,7 +17,6 @@ using osu.Game.Input.Bindings; using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Overlays { @@ -27,17 +24,17 @@ namespace osu.Game.Overlays { private const float offset = 10; - private VolumeMeter volumeMeterMaster; - private VolumeMeter volumeMeterEffect; - private VolumeMeter volumeMeterMusic; - private MuteButton muteButton; + private VolumeMeter volumeMeterMaster = null!; + private VolumeMeter volumeMeterEffect = null!; + private VolumeMeter volumeMeterMusic = null!; + private MuteButton muteButton = null!; + + private SelectionCycleFillFlowContainer volumeMeters = null!; private readonly BindableDouble muteAdjustment = new BindableDouble(); public Bindable IsMuted { get; } = new Bindable(); - private SelectionCycleFillFlowContainer volumeMeters; - [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -140,8 +137,6 @@ namespace osu.Game.Overlays return false; } - private ScheduledDelegate popOutDelegate; - public void FocusMasterVolume() { volumeMeters.Select(volumeMeterMaster); @@ -191,6 +186,8 @@ namespace osu.Game.Overlays base.OnHoverLost(e); } + private ScheduledDelegate? popOutDelegate; + private void schedulePopOut() { popOutDelegate?.Cancel(); From 37a296ba4c3808f8fdaf322ceba1387d9a05ebde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 11 Jul 2024 13:22:36 +0200 Subject: [PATCH 188/670] Limit per-frame movement hitobject processing to stacking updates --- .../Beatmaps/OsuBeatmapProcessor.cs | 17 +++++++++++------ .../Edit/OsuSelectionHandler.cs | 9 +++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index d335913586..0e77553177 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -42,7 +42,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { base.PostProcess(); - var hitObjects = Beatmap.HitObjects as List ?? Beatmap.HitObjects.OfType().ToList(); + ApplyStacking(Beatmap); + } + + internal static void ApplyStacking(IBeatmap beatmap) + { + var hitObjects = beatmap.HitObjects as List ?? beatmap.HitObjects.OfType().ToList(); if (hitObjects.Count > 0) { @@ -50,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps foreach (var h in hitObjects) h.StackHeight = 0; - if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) - applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1); + if (beatmap.BeatmapInfo.BeatmapVersion >= 6) + applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1); else - applyStackingOld(Beatmap.BeatmapInfo, hitObjects); + applyStackingOld(beatmap.BeatmapInfo, hitObjects); } } - private void applyStacking(BeatmapInfo beatmapInfo, List hitObjects, int startIndex, int endIndex) + private static void applyStacking(BeatmapInfo beatmapInfo, List hitObjects, int startIndex, int endIndex) { ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); ArgumentOutOfRangeException.ThrowIfNegative(startIndex); @@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } } - private void applyStackingOld(BeatmapInfo beatmapInfo, List hitObjects) + private static void applyStackingOld(BeatmapInfo beatmapInfo, List hitObjects) { for (int i = 0; i < hitObjects.Count; i++) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 9b4b77b625..2ca7664d5d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -72,10 +73,10 @@ namespace osu.Game.Rulesets.Osu.Edit // but this will be corrected. moveSelectionInBounds(); - // update all of the objects in order to update stacking. - // in particular, this causes stacked objects to instantly unstack on drag. - foreach (var h in hitObjects) - EditorBeatmap.Update(h); + // manually update stacking. + // this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons, + // as the entire flow is too expensive to run on every movement. + Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap); return true; } From 669e945fc334c544274c22b63ebb856ed394b37a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 14:56:17 +0300 Subject: [PATCH 189/670] Fix `ModSelectOverlay` not hiding without a footer --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index da93539679..4143a7fe8a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -604,7 +604,13 @@ namespace osu.Game.Overlays.Mods return base.OnPressed(e); - void hideOverlay() => footer?.BackButton.TriggerClick(); + void hideOverlay() + { + if (footer != null) + footer.BackButton.TriggerClick(); + else + Hide(); + } } /// From 7a5624fd0efd1c422afc4595b284bf8af444df3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:30:07 +0300 Subject: [PATCH 190/670] Add screen footer to `ScreenTestScene` --- .../Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 8 ++++---- osu.Game/Tests/Visual/ScreenTestScene.cs | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index f9ef085838..e2593e68e5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -312,14 +312,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); ClickButtonWhenEnabled(); - AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().Ranked.Value == false); - AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); + AddAssert("mod select shows unranked", () => this.ChildrenOfType().Single().Ranked.Value == false); + AddAssert("score multiplier = 1.20", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); AddStep("select flashlight", () => screen.UserModsSelectOverlay.ChildrenOfType().Single(m => m.Mod is ModFlashlight).TriggerClick()); - AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); + AddAssert("score multiplier = 1.35", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01)); AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200); - AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); + AddAssert("score multiplier = 1.20", () => this.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01)); } private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 3cca1e59cc..f780b1a8f8 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -12,6 +12,7 @@ using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Screens; +using osu.Game.Screens.Footer; namespace osu.Game.Tests.Visual { @@ -30,6 +31,9 @@ namespace osu.Game.Tests.Visual [Cached(typeof(IDialogOverlay))] protected DialogOverlay DialogOverlay { get; private set; } + [Cached] + private ScreenFooter footer; + protected ScreenTestScene() { base.Content.AddRange(new Drawable[] @@ -44,7 +48,8 @@ namespace osu.Game.Tests.Visual { RelativeSizeAxes = Axes.Both, Child = DialogOverlay = new DialogOverlay() - } + }, + footer = new ScreenFooter(), }); Stack.ScreenPushed += (_, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); From d4a4a059d4a68affe7563d63ede5c9112a919482 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:31:02 +0300 Subject: [PATCH 191/670] Fix footer content not accessible by overlay when overriden by a subclass --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 16 ++++++++-------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 8 ++++---- .../Overlays/Mods/ShearedOverlayContainer.cs | 6 +++++- osu.Game/Screens/Footer/ScreenFooter.cs | 6 ++++-- .../Screens/OnlinePlay/FreeModSelectOverlay.cs | 10 +++++----- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 2c8ceba82c..1a302cf51d 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays private ScreenStack? stack; - public ShearedButton? NextButton => currentFooterContent?.NextButton; + public ShearedButton? NextButton => DisplayedFooterContent?.NextButton; private readonly Bindable showFirstRunSetup = new Bindable(); @@ -148,17 +148,17 @@ namespace osu.Game.Overlays [Resolved] private ScreenFooter footer { get; set; } = null!; - private FirstRunSetupFooterContent? currentFooterContent; + public new FirstRunSetupFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FirstRunSetupFooterContent; public override VisibilityContainer CreateFooterContent() { - currentFooterContent = new FirstRunSetupFooterContent + var footerContent = new FirstRunSetupFooterContent { ShowNextStep = showNextStep, }; - currentFooterContent.OnLoadComplete += _ => updateButtons(); - return currentFooterContent; + footerContent.OnLoadComplete += _ => updateButtons(); + return footerContent; } public override bool OnBackButton() @@ -182,7 +182,7 @@ namespace osu.Game.Overlays switch (e.Action) { case GlobalAction.Select: - currentFooterContent?.NextButton.TriggerClick(); + DisplayedFooterContent?.NextButton.TriggerClick(); return true; case GlobalAction.Back: @@ -287,9 +287,9 @@ namespace osu.Game.Overlays updateButtons(); } - private void updateButtons() => currentFooterContent?.UpdateButtons(currentStepIndex, steps); + private void updateButtons() => DisplayedFooterContent?.UpdateButtons(currentStepIndex, steps); - private partial class FirstRunSetupFooterContent : VisibilityContainer + public partial class FirstRunSetupFooterContent : VisibilityContainer { public ShearedButton NextButton { get; private set; } = null!; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 4143a7fe8a..7469590895 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -254,9 +254,9 @@ namespace osu.Game.Overlays.Mods }); } - private ModSelectFooterContent? currentFooterContent; + public new ModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as ModSelectFooterContent; - public override VisibilityContainer CreateFooterContent() => currentFooterContent = new ModSelectFooterContent(this) + public override VisibilityContainer CreateFooterContent() => new ModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, @@ -270,7 +270,7 @@ namespace osu.Game.Overlays.Mods base.Update(); SearchTextBox.PlaceholderText = SearchTextBox.HasFocus ? input_search_placeholder : tab_to_search_placeholder; - aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = currentFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; + aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = DisplayedFooterContent?.DisplaysStackedVertically == true ? 75f : 15f }; } /// @@ -573,7 +573,7 @@ namespace osu.Game.Overlays.Mods { if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) { - currentFooterContent?.DeselectAllModsButton?.TriggerClick(); + DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; } diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 8c6b9e805b..dfa49f3779 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -102,6 +102,8 @@ namespace osu.Game.Overlays.Mods }; } + public VisibilityContainer? DisplayedFooterContent { get; private set; } + /// /// Creates content to be displayed on the game-wide footer. /// @@ -137,7 +139,8 @@ namespace osu.Game.Overlays.Mods if (footer != null) { - activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this); + activeOverlayRegistration = footer.RegisterActiveOverlayContainer(this, out var footerContent); + DisplayedFooterContent = footerContent; if (footer.State.Value == Visibility.Hidden) { @@ -160,6 +163,7 @@ namespace osu.Game.Overlays.Mods { activeOverlayRegistration?.Dispose(); activeOverlayRegistration = null; + DisplayedFooterContent = null; if (hideFooterOnPopOut) { diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index f8d222e510..4c020fc95e 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -190,7 +190,7 @@ namespace osu.Game.Screens.Footer private readonly List temporarilyHiddenButtons = new List(); - public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay) + public IDisposable RegisterActiveOverlayContainer(ShearedOverlayContainer overlay, out VisibilityContainer? footerContent) { if (activeOverlay != null) { @@ -219,7 +219,9 @@ namespace osu.Game.Screens.Footer updateColourScheme(overlay.ColourProvider.ColourScheme); - var content = overlay.CreateFooterContent() ?? Empty(); + footerContent = overlay.CreateFooterContent(); + + var content = footerContent ?? Empty(); Add(contentContainer = new Container { diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index 2b3ab94916..8937abb775 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -23,9 +23,7 @@ namespace osu.Game.Screens.OnlinePlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } - private FreeModSelectFooterContent? currentFooterContent; - - protected override SelectAllModsButton? SelectAllModsButton => currentFooterContent?.SelectAllModsButton; + protected override SelectAllModsButton? SelectAllModsButton => DisplayedFooterContent?.SelectAllModsButton; public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) @@ -35,13 +33,15 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType) => new ModColumn(modType, true); - public override VisibilityContainer CreateFooterContent() => currentFooterContent = new FreeModSelectFooterContent(this) + public new FreeModSelectFooterContent? DisplayedFooterContent => base.DisplayedFooterContent as FreeModSelectFooterContent; + + public override VisibilityContainer CreateFooterContent() => new FreeModSelectFooterContent(this) { Beatmap = { BindTarget = Beatmap }, ActiveMods = { BindTarget = ActiveMods }, }; - private partial class FreeModSelectFooterContent : ModSelectFooterContent + public partial class FreeModSelectFooterContent : ModSelectFooterContent { private readonly FreeModSelectOverlay overlay; From 3ea0f58daabed92d8e86191379f7d417f9176738 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 11 Jul 2024 15:31:23 +0300 Subject: [PATCH 192/670] Update `TestSceneFreeModSelectOverlay` to work again --- .../TestSceneFreeModSelectOverlay.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 938ab1e9f4..497faa28d0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -16,6 +17,7 @@ using osu.Framework.Testing; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Footer; using osu.Game.Screens.OnlinePlay; using osu.Game.Utils; using osuTK.Input; @@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { private FreeModSelectOverlay freeModSelectOverlay; private FooterButtonFreeMods footerButtonFreeMods; + private ScreenFooter footer; private readonly Bindable>> availableMods = new Bindable>>(); [BackgroundDependencyLoader] @@ -127,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { createFreeModSelect(); - AddAssert("overlay select all button enabled", () => freeModSelectOverlay.ChildrenOfType().Single().Enabled.Value); + AddAssert("overlay select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType().Any(t => t.Text == "off")); AddStep("click footer select all button", () => @@ -150,19 +153,27 @@ namespace osu.Game.Tests.Visual.Multiplayer private void createFreeModSelect() { - AddStep("create free mod select screen", () => Children = new Drawable[] + AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer { - freeModSelectOverlay = new FreeModSelectOverlay + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - State = { Value = Visibility.Visible } - }, - footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + freeModSelectOverlay = new FreeModSelectOverlay + { + State = { Value = Visibility.Visible } + }, + footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay) + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Y = -ScreenFooter.HEIGHT, + Current = { BindTarget = freeModSelectOverlay.SelectedMods }, + }, + footer = new ScreenFooter(), }, + CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) }, }); + AddUntilStep("all column content loaded", () => freeModSelectOverlay.ChildrenOfType().Any() && freeModSelectOverlay.ChildrenOfType().All(column => column.IsLoaded && column.ItemsLoaded)); From fb4f620c90409c19be1ae49893813f8bd2b95ae2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 12:34:43 +0900 Subject: [PATCH 193/670] Add back BPM and adjust sizing of bottom bar a bit more --- osu.Game/Screens/Edit/BottomBar.cs | 4 +- .../Edit/Components/TimeInfoContainer.cs | 46 ++++++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index a11f40c8fd..514a06f1c5 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.X; - Height = 40; + Height = 50; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 170), + new Dimension(GridSizeMode.Absolute, 150), new Dimension(), new Dimension(GridSizeMode.Absolute, 220), new Dimension(GridSizeMode.Absolute, HitObjectComposer.TOOLBOX_CONTRACTED_SIZE_RIGHT), diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 37facb3b95..8f2a3d49ca 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -17,6 +17,8 @@ namespace osu.Game.Screens.Edit.Components { public partial class TimeInfoContainer : BottomBarContainer { + private OsuSpriteText bpm = null!; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -24,16 +26,38 @@ namespace osu.Game.Screens.Edit.Components private EditorClock editorClock { get; set; } = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { Background.Colour = colourProvider.Background5; Children = new Drawable[] { new TimestampControl(), + bpm = new OsuSpriteText + { + Colour = colours.Orange1, + Anchor = Anchor.CentreLeft, + Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold), + Position = new Vector2(2, 4), + } }; } + private double? lastBPM; + + protected override void Update() + { + base.Update(); + + double newBPM = editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM; + + if (lastBPM != newBPM) + { + lastBPM = newBPM; + bpm.Text = @$"{newBPM:0} BPM"; + } + } + private partial class TimestampControl : OsuClickableContainer { private Container hoverLayer = null!; @@ -63,7 +87,8 @@ namespace osu.Game.Screens.Edit.Components RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Top = 5, + Top = 4, + Bottom = 1, Horizontal = -2 }, Child = new Container @@ -83,12 +108,13 @@ namespace osu.Game.Screens.Edit.Components Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Spacing = new Vector2(-2, 0), - Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light), + Font = OsuFont.Torus.With(size: 32, fixedWidth: true, weight: FontWeight.Light), }, - inputTextBox = new OsuTextBox + inputTextBox = new TimestampTextBox { - Width = 150, - Height = 36, + Position = new Vector2(-2, 4), + Width = 128, + Height = 26, Alpha = 0, CommitOnFocusLost = true, }, @@ -136,6 +162,14 @@ namespace osu.Game.Screens.Edit.Components showingHoverLayer = shouldShowHoverLayer; } } + + private partial class TimestampTextBox : OsuTextBox + { + public TimestampTextBox() + { + TextContainer.Height = 0.8f; + } + } } } } From e6b7d2530eef28859c4bde9fde5dc3e2c31afea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 13:39:40 +0900 Subject: [PATCH 194/670] Change red shade for timing control points --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 3360b1d1fa..db1d440f18 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -26,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// private const double default_beat_length = 60000.0 / 60.0; - public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red1; + public override Color4 GetRepresentingColour(OsuColour colours) => colours.Red2; public static readonly TimingControlPoint DEFAULT = new TimingControlPoint { From 685b19a5714435b3a9f9f3442504b74002a65122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 13:57:36 +0900 Subject: [PATCH 195/670] Adjust various visuals of summary timeline in a hope for user acceptance .. with a somewhat appealing design. --- .../Timelines/Summary/Parts/BreakPart.cs | 2 +- .../Parts/ControlPointVisualisation.cs | 4 +- .../Summary/Parts/EffectPointVisualisation.cs | 6 +-- .../Summary/Parts/GroupVisualisation.cs | 5 ++- .../Summary/Parts/PreviewTimePart.cs | 2 +- .../Timelines/Summary/SummaryTimeline.cs | 42 +++++++++---------- .../Visualisations/PointVisualisation.cs | 5 ++- 7 files changed, 36 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 100f37fd27..ef1a825969 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativeSizeAxes = Axes.Both; InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray7; + Colour = colours.Gray6; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 47169481e2..1df128461e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -15,7 +16,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public ControlPointVisualisation(ControlPoint point) { Point = point; - Width = 2; + Alpha = 0.3f; + Blending = BlendingParameters.Additive; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index a3885bc2cc..41f4b3a365 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -3,7 +3,6 @@ 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; @@ -96,10 +95,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, Origin = Anchor.CentreLeft, - Width = 1, - Height = 0.75f, + Height = 0.4f, Depth = float.MaxValue, - Colour = effect.GetRepresentingColour(colours).Darken(0.5f), + Colour = effect.GetRepresentingColour(colours), }); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index 2c806be162..e01900b129 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -39,7 +39,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts switch (point) { case TimingControlPoint: - AddInternal(new ControlPointVisualisation(point)); + AddInternal(new ControlPointVisualisation(point) + { + Y = -0.4f, + }); break; case EffectControlPoint effect: diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 407173034e..3a63d1e9b3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public PreviewTimeVisualisation(double time) : base(time) { - Width = 2; + Alpha = 0.8f; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 92012936bc..49110ccee3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -23,27 +23,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Children = new Drawable[] { - new ControlPointPart - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - new BookmarkPart - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, - new PreviewTimePart - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.35f - }, new Container { Name = "centre line", @@ -73,6 +52,27 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary }, } }, + new PreviewTimePart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.4f, + }, + new ControlPointPart + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.4f + }, + new BookmarkPart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, new BreakPart { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 3f0c125ada..571494860f 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -11,12 +11,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations /// public partial class PointVisualisation : Circle { + public readonly double StartTime; + public const float MAX_WIDTH = 4; public PointVisualisation(double startTime) : this() { X = (float)startTime; + StartTime = startTime; } public PointVisualisation() @@ -28,7 +31,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations Origin = Anchor.Centre; Width = MAX_WIDTH; - Height = 0.75f; + Height = 0.4f; } } } From 9a7a0cdb341f507ea3ed0f4f9e25f5a01aef6fbf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:29:18 +0900 Subject: [PATCH 196/670] Adjust timeline ticks to add a bit more body back --- .../Components/Timeline/TimelineTickDisplay.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 8de5087850..1f357283bd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -147,20 +147,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var line = getNextUsableLine(); line.X = xPos; - line.Width = PointVisualisation.MAX_WIDTH * size.X; - if (showTimingChanges.Value) - { - line.Anchor = Anchor.BottomLeft; - line.Origin = Anchor.BottomCentre; - line.Height = 0.7f + size.Y * 0.28f; - } - else - { - line.Anchor = Anchor.CentreLeft; - line.Origin = Anchor.Centre; - line.Height = 0.92f + size.Y * 0.07f; - } + line.Anchor = Anchor.CentreLeft; + line.Origin = Anchor.Centre; + + line.Height = 0.6f + size.Y * 0.4f; + line.Width = PointVisualisation.MAX_WIDTH * (0.6f + 0.4f * size.X); line.Colour = colour; } From 2ad2ae0c16a969bdb7337b85087d70d54f94f449 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:34:01 +0900 Subject: [PATCH 197/670] Add slight offset for timeline BPM display to avoid overlaps --- .../Compose/Components/Timeline/TimelineControlPointGroup.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index c1b6069523..98556fda45 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.TopLeft; - X = (float)group.Time; + // offset visually to avoid overlapping timeline tick display. + X = (float)group.Time + 6; } protected override void LoadComplete() From f65ab6736db5fd5854224ee7a00e6a383e025b1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 15:41:51 +0900 Subject: [PATCH 198/670] Adjust breaks in timeline to be centered with waveform / hitobjects --- .../Components/Timeline/TimelineBreak.cs | 32 +++---------------- .../Screens/Edit/Compose/ComposeScreen.cs | 8 ++++- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs index 29030099c8..721af97674 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBreak.cs @@ -29,11 +29,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action? OnDeleted { get; init; } - private Box background = null!; - - [Resolved] - private OsuColour colours { get; set; } = null!; - public TimelineBreak(BreakPeriod b) { Break.Value = b; @@ -53,11 +48,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5 }, - Child = background = new Box + Child = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Gray5, - Alpha = 0.7f, + Alpha = 0.9f, }, }, new DragHandle(isStartHandle: true) @@ -88,23 +83,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, true); } - protected override bool OnHover(HoverEvent e) - { - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); - } - - private void updateState() - { - background.FadeColour(IsHovered ? colours.Gray6 : colours.Gray5, 400, Easing.OutQuint); - } - public MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => OnDeleted?.Invoke(Break.Value)), @@ -159,7 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Anchor = Anchor, Origin = Anchor, RelativeSizeAxes = Axes.Y, - CornerRadius = 5, + CornerRadius = 4, Masking = true, Child = new Box { @@ -249,8 +227,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (active) colour = colour.Lighten(0.3f); - this.FadeColour(colour, 400, Easing.OutQuint); - handle.ResizeWidthTo(active ? 20 : 10, 400, Easing.OutElasticHalf); + handle.FadeColour(colour, 400, Easing.OutQuint); + handle.ResizeWidthTo(active ? 10 : 8, 400, Easing.OutElasticHalf); } } } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 9b945e1d6d..cc33840929 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -75,7 +75,13 @@ namespace osu.Game.Screens.Edit.Compose Children = new Drawable[] { new TimelineBlueprintContainer(composer), - new TimelineBreakDisplay { RelativeSizeAxes = Axes.Both, }, + new TimelineBreakDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.75f, + }, } }); } From ca2fc7295967bec77de252d39091e04acdccadd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:26:38 +0900 Subject: [PATCH 199/670] Adjust timeline centre marker visuals and bring in front of ticks --- .../Compose/Components/Timeline/CentreMarker.cs | 17 ++++++++++------- .../Compose/Components/Timeline/Timeline.cs | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index be1888684e..e3542cbf9b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -3,10 +3,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -23,7 +25,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; + } + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colours) + { InternalChildren = new Drawable[] { new Box @@ -32,21 +38,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Width = bar_width, + Blending = BlendingParameters.Additive, + Colour = ColourInfo.GradientVertical(colours.Colour2, Color4.Black), }, new Triangle { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Size = new Vector2(triangle_width, triangle_width * 0.8f), - Scale = new Vector2(1, -1) + Scale = new Vector2(1, -1), + Colour = colours.Colour2, }, }; } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colours) - { - Colour = colours.Highlight1; - } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 05e44d4737..6ce5c06801 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -125,8 +125,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline MidColour = colours.BlueDark, HighColour = colours.BlueDarker, }, - centreMarker.CreateProxy(), ticks.CreateProxy(), + centreMarker.CreateProxy(), new Box { Name = "zero marker", From 43addc84003c32caa2b5417143f5843dace4cba9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:28:31 +0900 Subject: [PATCH 200/670] Fix test regression --- osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index 3319788c8a..ad8c29d180 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1); AddAssert("preview time line should not show", () => !Editor.ChildrenOfType().Single().Children.Any()); AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000); - AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha == 1); + AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha, () => Is.GreaterThan(0)); } } } From 275e7aa451f5f9a0d8de2889a307903fa653124e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 16:26:38 +0900 Subject: [PATCH 201/670] Adjust timeline centre marker visuals and bring in front of ticks --- .../Screens/Edit/Compose/Components/Timeline/CentreMarker.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index e3542cbf9b..3d76656a14 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; From 24547226019dc7a080f317b436a98c5765f82bb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 14:18:41 +0900 Subject: [PATCH 202/670] Add tooltips to summary timeline display --- .../Timelines/Summary/Parts/BreakPart.cs | 10 +++++++- .../Parts/ControlPointVisualisation.cs | 24 ++++++++++++++++++- .../Summary/Parts/EffectPointVisualisation.cs | 19 ++++++++++++++- .../Summary/Parts/PreviewTimePart.cs | 7 +++++- .../Visualisations/PointVisualisation.cs | 10 +++----- .../Timeline/TimelineTickDisplay.cs | 2 +- 6 files changed, 60 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index ef1a825969..c20738bbd9 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -4,9 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps.Timing; +using osu.Game.Extensions; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -46,12 +49,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts }, true); } - private partial class BreakVisualisation : PoolableDrawable + private partial class BreakVisualisation : PoolableDrawable, IHasTooltip { + private BreakPeriod breakPeriod; + public BreakPeriod BreakPeriod { set { + breakPeriod = value; X = (float)value.StartTime; Width = (float)value.Duration; } @@ -66,6 +72,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; Colour = colours.Gray6; } + + public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 1df128461e..977aadd6c3 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -2,18 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { - public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation + public partial class ControlPointVisualisation : PointVisualisation, IControlPointVisualisation, IHasTooltip { protected readonly ControlPoint Point; public ControlPointVisualisation(ControlPoint point) + : base(point.Time) { Point = point; Alpha = 0.3f; @@ -27,5 +32,22 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } public bool IsVisuallyRedundant(ControlPoint other) => other.GetType() == Point.GetType(); + + public LocalisableString TooltipText + { + get + { + switch (Point) + { + case EffectControlPoint effect: + return $"{StartTime.ToEditorFormattedString()} effect [{effect.ScrollSpeed:N2}x scroll{(effect.KiaiMode ? " kiai" : "")}]"; + + case TimingControlPoint timing: + return $"{StartTime.ToEditorFormattedString()} timing [{timing.BPM:N2} bpm {timing.TimeSignature.GetDescription()}]"; + } + + return string.Empty; + } + } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 41f4b3a365..f1e2b52ad8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -5,8 +5,11 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -90,7 +93,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Width = (float)(nextControlPoint.Time - effect.Time); - AddInternal(new Circle + AddInternal(new KiaiVisualisation(effect.Time, nextControlPoint.Time) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, @@ -102,6 +105,20 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } } + private partial class KiaiVisualisation : Circle, IHasTooltip + { + private readonly double startTime; + private readonly double endTime; + + public KiaiVisualisation(double startTime, double endTime) + { + this.startTime = startTime; + this.endTime = endTime; + } + + public LocalisableString TooltipText => $"{startTime.ToEditorFormattedString()} - {endTime.ToEditorFormattedString()} kiai time"; + } + // kiai sections display duration, so are required to be visualised. public bool IsVisuallyRedundant(ControlPoint other) => other is EffectControlPoint otherEffect && effect.KiaiMode == otherEffect.KiaiMode; } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 3a63d1e9b3..67bb1ef500 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -3,6 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -27,7 +30,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts }, true); } - private partial class PreviewTimeVisualisation : PointVisualisation + private partial class PreviewTimeVisualisation : PointVisualisation, IHasTooltip { public PreviewTimeVisualisation(double time) : base(time) @@ -37,6 +40,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.Green1; + + public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} preview time"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 571494860f..9c16f457f7 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -16,13 +16,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations public const float MAX_WIDTH = 4; public PointVisualisation(double startTime) - : this() - { - X = (float)startTime; - StartTime = startTime; - } - - public PointVisualisation() { RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Y; @@ -32,6 +25,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations Width = MAX_WIDTH; Height = 0.4f; + + X = (float)startTime; + StartTime = startTime; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 1f357283bd..def528d9e5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { PointVisualisation point; if (drawableIndex >= Count) - Add(point = new PointVisualisation()); + Add(point = new PointVisualisation(0)); else point = Children[drawableIndex]; From d2cb07b15796d24e2b20753a06c520d059131ad7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 17:22:36 +0900 Subject: [PATCH 203/670] Add a bit more padding between node overlays and hitobjects in timeline --- .../Edit/Compose/Components/Timeline/DifficultyPointPiece.cs | 1 + .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 1 + .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs index 3ad6095965..44235e5d0b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/DifficultyPointPiece.cs @@ -33,6 +33,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public DifficultyPointPiece(HitObject hitObject) { HitObject = hitObject; + Y = -2.5f; speedMultiplier = (hitObject as IHasSliderVelocity)?.SliderVelocityMultiplierBindable.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 1f9c7a891b..8c7603021a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -35,6 +35,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public SamplePointPiece(HitObject hitObject) { HitObject = hitObject; + Y = 2.5f; } public bool AlternativeColor { get; init; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 753856199a..a168dcbd3e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -519,7 +519,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { Type = EdgeEffectType.Shadow, Radius = 5, - Colour = Color4.Black.Opacity(0.4f) + Colour = Color4.Black.Opacity(0.05f) } }; } From 4be5d056c573234912aafb5b19f769fd8e81afca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 17:23:48 +0900 Subject: [PATCH 204/670] Reduce opacity of centre marker slightly --- .../Screens/Edit/Compose/Components/Timeline/CentreMarker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index e3542cbf9b..7d8622905c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Width = bar_width, Blending = BlendingParameters.Additive, - Colour = ColourInfo.GradientVertical(colours.Colour2, Color4.Black), + Colour = ColourInfo.GradientVertical(colours.Colour2.Opacity(0.6f), colours.Colour2.Opacity(0)), }, new Triangle { From 2453e2fd00b3b484ccc9f14e1555c87fc0f6beee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 18:11:23 +0900 Subject: [PATCH 205/670] Fix nullability issue --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index c20738bbd9..17e0d47676 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private partial class BreakVisualisation : PoolableDrawable, IHasTooltip { - private BreakPeriod breakPeriod; + private BreakPeriod breakPeriod = null!; public BreakPeriod BreakPeriod { From 217b01d3031b550e5e35f9a39142dbe9da27fd84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 12 Jul 2024 11:10:52 +0200 Subject: [PATCH 206/670] Apply tooltip to bookmark pieces too Bookmarks don't show on real beatmaps, but they do show in test scenes (namely `TestSceneEditorSummaryTimeline`). Also does some more changes to adjust the markers to the latest updates to other markers. --- .../Components/Timelines/Summary/Parts/BookmarkPart.cs | 8 ++++++-- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index ea71f24e9c..189cb4ba4a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -2,6 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; @@ -19,16 +22,17 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Add(new BookmarkVisualisation(bookmark)); } - private partial class BookmarkVisualisation : PointVisualisation + private partial class BookmarkVisualisation : PointVisualisation, IHasTooltip { public BookmarkVisualisation(double startTime) : base(startTime) { - Width = 2; } [BackgroundDependencyLoader] private void load(OsuColour colours) => Colour = colours.Blue; + + public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} bookmark"; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 49110ccee3..a495442c1d 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Anchor = Anchor.Centre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, - Height = 0.35f + Height = 0.4f }, new BreakPart { From ad2b354d9c2543d4bedc1316b9401b455703e3c3 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Jul 2024 17:01:07 +0900 Subject: [PATCH 207/670] Update sample looping behaviour to better suit new sample --- osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs index fec36fa7fa..d84e1d760d 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Dialog protected override void LoadComplete() { base.LoadComplete(); - Progress.BindValueChanged(progressChanged, true); + Progress.BindValueChanged(progressChanged); } protected override void Confirm() @@ -114,13 +114,13 @@ namespace osu.Game.Overlays.Dialog if (progress.NewValue < progress.OldValue) return; - if (Clock.CurrentTime - lastTickPlaybackTime < 30) + if (Clock.CurrentTime - lastTickPlaybackTime < 40) return; var channel = tickSample.GetChannel(); - channel.Frequency.Value = 1 + progress.NewValue * 0.5f; - channel.Volume.Value = 0.5f + progress.NewValue / 2f; + channel.Frequency.Value = 1 + progress.NewValue; + channel.Volume.Value = 0.1f + progress.NewValue / 2f; channel.Play(); From 320df7da2b564b53820860439ac39f64499b56a5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 11 Jul 2024 18:22:27 +0900 Subject: [PATCH 208/670] Use separate samples for scrolling to top and scrolling to previous --- .../Graphics/UserInterface/HoverSampleSet.cs | 6 ------ osu.Game/Overlays/OverlayScrollContainer.cs | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 72d50eb042..5b0fbc693e 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -16,15 +16,9 @@ namespace osu.Game.Graphics.UserInterface [Description("button-sidebar")] ButtonSidebar, - [Description("toolbar")] - Toolbar, - [Description("tabselect")] TabSelect, - [Description("scrolltotop")] - ScrollToTop, - [Description("dialog-cancel")] DialogCancel, diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs index a99cf08abb..4328977a8d 100644 --- a/osu.Game/Overlays/OverlayScrollContainer.cs +++ b/osu.Game/Overlays/OverlayScrollContainer.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -112,8 +114,12 @@ namespace osu.Game.Overlays public Bindable LastScrollTarget = new Bindable(); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + + private Sample scrollToTopSample; + private Sample scrollToPreviousSample; + public ScrollBackButton() - : base(HoverSampleSet.ScrollToTop) { Size = new Vector2(50); Alpha = 0; @@ -150,11 +156,14 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OverlayColourProvider colourProvider, AudioManager audio) { IdleColour = colourProvider.Background6; HoverColour = colourProvider.Background5; flashColour = colourProvider.Light1; + + scrollToTopSample = audio.Samples.Get(@"UI/scroll-to-top"); + scrollToPreviousSample = audio.Samples.Get(@"UI/scroll-to-previous"); } protected override void LoadComplete() @@ -171,6 +180,12 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { background.FlashColour(flashColour, 800, Easing.OutQuint); + + if (LastScrollTarget.Value == null) + scrollToTopSample?.Play(); + else + scrollToPreviousSample?.Play(); + return base.OnClick(e); } From e8f7213b3b2907c1c2aee8d0d4be92aeee23f0c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 19:20:41 +0900 Subject: [PATCH 209/670] Move logo depth to a forward passing --- osu.Game/OsuGame.cs | 3 +-- osu.Game/Screens/Footer/ScreenFooter.cs | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dfd9f41e1a..2580e44e73 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -297,8 +297,6 @@ namespace osu.Game if (hideToolbar) Toolbar.Hide(); } - public void ChangeLogoDepth(bool inFrontOfFooter) => ScreenContainer.ChangeChildDepth(logoContainer, inFrontOfFooter ? float.MinValue : 0); - protected override UserInputManager CreateUserInputManager() { var userInputManager = base.CreateUserInputManager(); @@ -996,6 +994,7 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Child = ScreenFooter = new ScreenFooter(backReceptor) { + RequestLogoInFront = inFront => ScreenContainer.ChangeChildDepth(logoContainer, inFront ? float.MinValue : 0), OnBack = () => { if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 4c020fc95e..6a1efcf87a 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Footer public ScreenBackButton BackButton { get; private set; } = null!; + public Action? RequestLogoInFront { get; set; } + public Action? OnBack; public ScreenFooter(BackReceptor? receptor = null) @@ -114,7 +116,7 @@ namespace osu.Game.Screens.Footer changeLogoDepthDelegate = null; logoTrackingContainer.StartTracking(logo, duration, easing); - game?.ChangeLogoDepth(inFrontOfFooter: true); + RequestLogoInFront?.Invoke(true); } public void StopTrackingLogo() @@ -122,7 +124,7 @@ namespace osu.Game.Screens.Footer logoTrackingContainer.StopTracking(); if (game != null) - changeLogoDepthDelegate = Scheduler.AddDelayed(() => game.ChangeLogoDepth(inFrontOfFooter: false), transition_duration); + changeLogoDepthDelegate = Scheduler.AddDelayed(() => RequestLogoInFront?.Invoke(false), transition_duration); } protected override void PopIn() From 9a1939a922c29657e60745348469f024c00ff99f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jul 2024 21:25:21 +0900 Subject: [PATCH 210/670] Move `logoContainer` local again --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2580e44e73..388a98d947 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -84,7 +84,7 @@ namespace osu.Game public partial class OsuGame : OsuGameBase, IKeyBindingHandler, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager, ILinkHandler { #if DEBUG - // Different port allows runnning release and debug builds alongside each other. + // Different port allows running release and debug builds alongside each other. public const int IPC_PORT = 44824; #else public const int IPC_PORT = 44823; @@ -137,8 +137,6 @@ namespace osu.Game protected ScalingContainer ScreenContainer { get; private set; } - private Container logoContainer; - protected Container ScreenOffsetContainer { get; private set; } private Container overlayOffsetContainer; @@ -954,6 +952,8 @@ namespace osu.Game Add(sessionIdleTracker); + Container logoContainer; + AddRange(new Drawable[] { new VolumeControlReceptor From 3eaac11b4426de652087eb1f18d9e4496eb941d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:26:45 +0300 Subject: [PATCH 211/670] Add profile hue attribute to API model --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 4a31718f28..1c07b38667 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -201,6 +201,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"playmode")] public string PlayMode; + [JsonProperty(@"profile_hue")] + [CanBeNull] + public int? ProfileHue; + [JsonProperty(@"profile_order")] public string[] ProfileOrder; From 933626a64b855fced565cc2f4ccc516abac73da5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:46:39 +0300 Subject: [PATCH 212/670] Support using custom hue in `OverlayColourProvider` --- osu.Game/Overlays/OverlayColourProvider.cs | 57 +++++----------------- 1 file changed, 11 insertions(+), 46 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 06b42eafc0..3b0af77365 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.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 System; using osuTK; using osuTK.Graphics; @@ -59,54 +58,20 @@ namespace osu.Game.Overlays private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); - // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 - private static float getBaseHue(OverlayColourScheme colourScheme) - { - switch (colourScheme) - { - default: - throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}."); - - case OverlayColourScheme.Red: - return 0; - - case OverlayColourScheme.Pink: - return 333 / 360f; - - case OverlayColourScheme.Orange: - return 45 / 360f; - - case OverlayColourScheme.Lime: - return 90 / 360f; - - case OverlayColourScheme.Green: - return 125 / 360f; - - case OverlayColourScheme.Aquamarine: - return 160 / 360f; - - case OverlayColourScheme.Purple: - return 255 / 360f; - - case OverlayColourScheme.Blue: - return 200 / 360f; - - case OverlayColourScheme.Plum: - return 320 / 360f; - } - } + private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; } + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 public enum OverlayColourScheme { - Red, - Pink, - Orange, - Lime, - Green, - Purple, - Blue, - Plum, - Aquamarine + Red = 0, + Orange = 45, + Lime = 90, + Green = 125, + Aquamarine = 160, + Blue = 200, + Purple = 255, + Plum = 320, + Pink = 333, } } From b292bf383205fa5d5994b95355a22abfa1d87f07 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:47:36 +0300 Subject: [PATCH 213/670] Support using custom hue in user profile overlay --- osu.Game/Overlays/FullscreenOverlay.cs | 29 ++++-- osu.Game/Overlays/UserProfileOverlay.cs | 117 +++++++++++++----------- 2 files changed, 87 insertions(+), 59 deletions(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 6ddf1eecf0..2a09147c76 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.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.Diagnostics.CodeAnalysis; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays public virtual LocalisableString Title => Header.Title.Title; public virtual LocalisableString Description => Header.Title.Description; - public T Header { get; } + public T Header { get; private set; } protected virtual Color4 BackgroundColour => ColourProvider.Background5; @@ -34,11 +35,12 @@ namespace osu.Game.Overlays protected override Container Content => content; + private readonly Box background; private readonly Container content; protected FullscreenOverlay(OverlayColourScheme colourScheme) { - Header = CreateHeader(); + RecreateHeader(); ColourProvider = new OverlayColourProvider(colourScheme); @@ -60,10 +62,9 @@ namespace osu.Game.Overlays base.Content.AddRange(new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = BackgroundColour }, content = new Container { @@ -75,14 +76,17 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - Waves.FirstWaveColour = ColourProvider.Light4; - Waves.SecondWaveColour = ColourProvider.Light3; - Waves.ThirdWaveColour = ColourProvider.Dark4; - Waves.FourthWaveColour = ColourProvider.Dark3; + UpdateColours(); } protected abstract T CreateHeader(); + [MemberNotNull(nameof(Header))] + protected void RecreateHeader() + { + Header = CreateHeader(); + } + public override void Show() { if (State.Value == Visibility.Visible) @@ -96,6 +100,15 @@ namespace osu.Game.Overlays } } + public void UpdateColours() + { + Waves.FirstWaveColour = ColourProvider.Light4; + Waves.SecondWaveColour = ColourProvider.Light3; + Waves.ThirdWaveColour = ColourProvider.Dark4; + Waves.FourthWaveColour = ColourProvider.Dark3; + background.Colour = BackgroundColour; + } + protected override void PopIn() { base.PopIn(); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 9840551d9f..c8b64bf2f1 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -99,11 +99,11 @@ namespace osu.Game.Overlays if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; - if (sectionsContainer != null) - sectionsContainer.ExpandableHeader = null; + sectionsContainer?.ScrollToTop(); + sectionsContainer?.Clear(); + tabs?.Clear(); userReq?.Cancel(); - Clear(); lastSection = null; sections = !user.IsBot @@ -119,20 +119,74 @@ namespace osu.Game.Overlays } : Array.Empty(); - tabs = new ProfileSectionTabControl - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }; + setupBaseContent(OverlayColourScheme.Pink); - Add(new OsuContextMenuContainer + if (API.State.Value != APIState.Offline) + { + userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); + userReq.Success += u => userLoadComplete(u, ruleset); + + API.Queue(userReq); + loadingLayer.Show(); + } + } + + private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) + { + Debug.Assert(sections != null && sectionsContainer != null && tabs != null); + + // reuse header and content if same colour scheme, otherwise recreate both. + var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; + if (profileScheme != ColourProvider.ColourScheme) + setupBaseContent(profileScheme); + + var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); + + var userProfile = new UserProfileData(loadedUser, actualRuleset); + Header.User.Value = userProfile; + + if (loadedUser.ProfileOrder != null) + { + foreach (string id in loadedUser.ProfileOrder) + { + var sec = sections.FirstOrDefault(s => s.Identifier == id); + + if (sec != null) + { + sec.User.Value = userProfile; + + sectionsContainer.Add(sec); + tabs.AddItem(sec); + } + } + } + + loadingLayer.Hide(); + } + + private void setupBaseContent(OverlayColourScheme colourScheme) + { + var previousColourScheme = ColourProvider.ColourScheme; + ColourProvider.ChangeColourScheme(colourScheme); + + if (sectionsContainer != null && colourScheme == previousColourScheme) + return; + + RecreateHeader(); + UpdateColours(); + + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = sectionsContainer = new ProfileSectionsContainer { ExpandableHeader = Header, - FixedHeader = tabs, + FixedHeader = tabs = new ProfileSectionTabControl + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, HeaderBackground = new Box { // this is only visible as the ProfileTabControl background @@ -140,7 +194,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both }, } - }); + }; sectionsContainer.SelectedSection.ValueChanged += section => { @@ -167,45 +221,6 @@ namespace osu.Game.Overlays sectionsContainer.ScrollTo(lastSection); } }; - - sectionsContainer.ScrollToTop(); - - if (API.State.Value != APIState.Offline) - { - userReq = user.OnlineID > 1 ? new GetUserRequest(user.OnlineID, ruleset) : new GetUserRequest(user.Username, ruleset); - userReq.Success += u => userLoadComplete(u, ruleset); - - API.Queue(userReq); - loadingLayer.Show(); - } - } - - private void userLoadComplete(APIUser loadedUser, IRulesetInfo? userRuleset) - { - Debug.Assert(sections != null && sectionsContainer != null && tabs != null); - - var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); - - var userProfile = new UserProfileData(loadedUser, actualRuleset); - Header.User.Value = userProfile; - - if (loadedUser.ProfileOrder != null) - { - foreach (string id in loadedUser.ProfileOrder) - { - var sec = sections.FirstOrDefault(s => s.Identifier == id); - - if (sec != null) - { - sec.User.Value = userProfile; - - sectionsContainer.Add(sec); - tabs.AddItem(sec); - } - } - } - - loadingLayer.Hide(); } private partial class ProfileSectionTabControl : OsuTabControl From be1d3c0ea4e0fc3527c0d660e6432481bc0f9c3c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 11:47:48 +0300 Subject: [PATCH 214/670] Add test coverage --- .../Online/TestSceneUserProfileOverlay.cs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index fa68c931d8..8dbd493920 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -111,6 +111,87 @@ namespace osu.Game.Tests.Visual.Online AddStep("complete request", () => pendingRequest.TriggerSuccess(TEST_USER)); } + [Test] + public void TestCustomColourScheme() + { + int hue = 0; + + AddSliderStep("hue", 0, 360, 222, h => hue = h); + + AddStep("set up request handling", () => + { + dummyAPI.HandleRequest = req => + { + if (req is GetUserRequest getUserRequest) + { + getUserRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue, + }); + return true; + } + + return false; + }; + }); + + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + } + + [Test] + public void TestCustomColourSchemeWithReload() + { + int hue = 0; + GetUserRequest pendingRequest = null!; + + AddSliderStep("hue", 0, 360, 222, h => hue = h); + + AddStep("set up request handling", () => + { + dummyAPI.HandleRequest = req => + { + if (req is GetUserRequest getUserRequest) + { + pendingRequest = getUserRequest; + return true; + } + + return false; + }; + }); + + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + + AddWaitStep("wait some", 3); + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue, + })); + + int hue2 = 0; + + AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h); + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + AddWaitStep("wait some", 3); + + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue2}", + Id = 1, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue2, + })); + } + public static readonly APIUser TEST_USER = new APIUser { Username = @"Somebody", From 9ed97d03a8143e74064ddb76f07f6dcd61af04ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jul 2024 20:22:52 +0900 Subject: [PATCH 215/670] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 447c783b29..4d9e7dea33 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From b12790c684bd0474d3f36c18cb71dd21c2922c92 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 18:29:03 +0300 Subject: [PATCH 216/670] Fix hue number 360 giving off a gray colour scheme They say it's not a bug, it's a feature...I dunno maybe later. --- osu.Game/Overlays/OverlayColourProvider.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 3b0af77365..5b6579e6cf 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -58,7 +58,12 @@ namespace osu.Game.Overlays private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); - private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; + private static float getBaseHue(OverlayColourScheme colourScheme) + { + // intentionally round hue number back to zero when it's 360, because that number apparently gives off a nice-looking gray colour scheme but is totally against expectation (maybe we can use this one day). + int hueNumber = (int)colourScheme % 360; + return hueNumber / 360f; + } } // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 From 43d08f702aa550aa2e41117c8970ae2b6fc0865d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 13 Jul 2024 18:43:46 +0300 Subject: [PATCH 217/670] Or just use `Colour4` where we have that fixed --- osu.Game/Overlays/OverlayColourProvider.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 5b6579e6cf..9613bc2857 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.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; namespace osu.Game.Overlays @@ -56,14 +55,9 @@ namespace osu.Game.Overlays ColourScheme = colourScheme; } - private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(ColourScheme), saturation, lightness, 1)); + private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(getBaseHue(ColourScheme), saturation, lightness); - private static float getBaseHue(OverlayColourScheme colourScheme) - { - // intentionally round hue number back to zero when it's 360, because that number apparently gives off a nice-looking gray colour scheme but is totally against expectation (maybe we can use this one day). - int hueNumber = (int)colourScheme % 360; - return hueNumber / 360f; - } + private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; } // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 From 2c102fc9d01726f84d185f0da1373e38f5c0163b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jul 2024 23:54:10 +0900 Subject: [PATCH 218/670] Fix test failure in `TestMetadataTransferred` --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 16e66cb2c5..a47da4d505 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -479,6 +479,7 @@ namespace osu.Game.Tests.Database using var rulesets = new RealmRulesetStore(realm, storage); using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => { // arbitrary beatmap removal @@ -496,7 +497,7 @@ namespace osu.Game.Tests.Database Debug.Assert(importAfterUpdate != null); Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); - Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded).Within(TimeSpan.FromSeconds(1))); }); } From adb803c7a9b41d60e27f9965e470ee83921bae70 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 14 Jul 2024 15:19:26 +0300 Subject: [PATCH 219/670] Force recreating sections container when loading new user to avoid weird UX when scrolled away --- osu.Game/Overlays/UserProfileOverlay.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c8b64bf2f1..8c750b5d83 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -99,9 +99,8 @@ namespace osu.Game.Overlays if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; - sectionsContainer?.ScrollToTop(); - sectionsContainer?.Clear(); - tabs?.Clear(); + if (sectionsContainer != null) + sectionsContainer.ExpandableHeader = null; userReq?.Cancel(); lastSection = null; @@ -119,7 +118,7 @@ namespace osu.Game.Overlays } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink); + setupBaseContent(OverlayColourScheme.Pink, forceContentRecreation: true); if (API.State.Value != APIState.Offline) { @@ -138,7 +137,7 @@ namespace osu.Game.Overlays // reuse header and content if same colour scheme, otherwise recreate both. var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; if (profileScheme != ColourProvider.ColourScheme) - setupBaseContent(profileScheme); + setupBaseContent(profileScheme, forceContentRecreation: false); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -164,17 +163,19 @@ namespace osu.Game.Overlays loadingLayer.Hide(); } - private void setupBaseContent(OverlayColourScheme colourScheme) + private void setupBaseContent(OverlayColourScheme colourScheme, bool forceContentRecreation) { var previousColourScheme = ColourProvider.ColourScheme; ColourProvider.ChangeColourScheme(colourScheme); - if (sectionsContainer != null && colourScheme == previousColourScheme) + if (colourScheme != previousColourScheme) + { + RecreateHeader(); + UpdateColours(); + } + else if (!forceContentRecreation) return; - RecreateHeader(); - UpdateColours(); - Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, From 6cdcd6136d157f91b17a4a7a6ff579a516705ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jul 2024 21:02:54 +0900 Subject: [PATCH 220/670] Fix editor toolboxes being incorrectly chopped --- osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs index 36cbf49885..c2ab5a6eb9 100644 --- a/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs +++ b/osu.Game/Rulesets/Edit/ExpandingToolboxContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Y; FillFlow.Spacing = new Vector2(5); - Padding = new MarginPadding { Vertical = 5 }; + FillFlow.Padding = new MarginPadding { Vertical = 5 }; } protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && anyToolboxHovered(screenSpacePos); From 1083e71ce6aadd9b9921b7dfbc8f1502390ccfd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 03:02:04 +0900 Subject: [PATCH 221/670] Fix potential crash when exiting daily challenge screen Without the schedule this will potentially run after disposal of the local drawable hierarchy. Closes https://github.com/ppy/osu/issues/28875. --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 4d4ae755fc..2b2c3a5e1f 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); - request.Success += req => + request.Success += req => Schedule(() => { var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray(); var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); @@ -165,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } userBestHeader.FadeTo(userBest == null ? 0 : 1); - }; + }); loadingLayer.Show(); scoreFlow.FadeTo(0.5f, 400, Easing.OutQuint); From bd4f3e28d90127b9b3a99d9594bb9056c7dea20f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 17:32:59 +0900 Subject: [PATCH 222/670] Fix judgement animation getting cut early --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 5 +++++ osu.Game/Rulesets/UI/JudgementContainer.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 37a9766b71..189be44033 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Judgements public DrawableHitObject? JudgedObject { get; private set; } + public HitObject? JudgedHitObject { get; private set; } + public override bool RemoveCompletedTransforms => false; protected SkinnableDrawable? JudgementBody { get; private set; } @@ -98,6 +101,7 @@ namespace osu.Game.Rulesets.Judgements { Result = result; JudgedObject = judgedObject; + JudgedHitObject = judgedObject?.HitObject; } protected override void FreeAfterUse() @@ -105,6 +109,7 @@ namespace osu.Game.Rulesets.Judgements base.FreeAfterUse(); JudgedObject = null; + JudgedHitObject = null; } protected override void PrepareForUse() diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 886dd34fc7..86ab213ca1 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.UI // remove any existing judgements for the judged object. // this can be the case when rewinding. - RemoveAll(c => c.JudgedObject == judgement.JudgedObject, false); + RemoveAll(c => c.JudgedHitObject == judgement.JudgedHitObject, false); base.Add(judgement); } From 063377f47cd96bfff5a27a7c5e4c10220badc5ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 17:45:25 +0900 Subject: [PATCH 223/670] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 9fd0df3036..fe0a452e92 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 48d9c2564a..acfcae7c93 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From d4ea604ad081788028352a55327e8fef72be2d91 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 18:14:37 +0900 Subject: [PATCH 224/670] Add test --- .../Gameplay/TestSceneJudgementContainer.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs new file mode 100644 index 0000000000..508877859c --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs @@ -0,0 +1,98 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneJudgementContainer : OsuTestScene + { + private JudgementContainer judgementContainer = null!; + + [SetUpSteps] + public void SetUp() + { + AddStep("create judgement container", () => Child = judgementContainer = new JudgementContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + + [Test] + public void TestJudgementFromSameHitObjectIsRemoved() + { + DrawableHitCircle drawableHitCircle1 = null!; + DrawableHitCircle drawableHitCircle2 = null!; + + AddStep("create hit circles", () => + { + Add(drawableHitCircle1 = new DrawableHitCircle(createHitCircle())); + Add(drawableHitCircle2 = new DrawableHitCircle(createHitCircle())); + }); + + int judgementCount = 0; + + AddStep("judge the same hitobject twice via different drawables", () => + { + addDrawableJudgement(drawableHitCircle1); + drawableHitCircle2.Apply(drawableHitCircle1.HitObject); + addDrawableJudgement(drawableHitCircle2); + judgementCount = judgementContainer.Count; + }); + + AddAssert("one judgement in container", () => judgementCount, () => Is.EqualTo(1)); + } + + [Test] + public void TestJudgementFromDifferentHitObjectIsNotRemoved() + { + DrawableHitCircle drawableHitCircle = null!; + + AddStep("create hit circle", () => Add(drawableHitCircle = new DrawableHitCircle(createHitCircle()))); + + int judgementCount = 0; + + AddStep("judge two hitobjects via the same drawable", () => + { + addDrawableJudgement(drawableHitCircle); + drawableHitCircle.Apply(createHitCircle()); + addDrawableJudgement(drawableHitCircle); + judgementCount = judgementContainer.Count; + }); + + AddAssert("two judgements in container", () => judgementCount, () => Is.EqualTo(2)); + } + + private void addDrawableJudgement(DrawableHitObject drawableHitObject) + { + var judgement = new DrawableOsuJudgement(); + + judgement.Apply(new JudgementResult(drawableHitObject.HitObject, new OsuJudgement()) + { + Type = HitResult.Great, + TimeOffset = Time.Current + }, drawableHitObject); + + judgementContainer.Add(judgement); + } + + private HitCircle createHitCircle() + { + var circle = new HitCircle(); + circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return circle; + } + } +} From 4ad7d900c17ed3de6e405f6f25d29f2b5f40bb5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 18:20:33 +0900 Subject: [PATCH 225/670] Fix incorrect editor screen padding --- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index cab0ba9bcb..d40db329ec 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -328,7 +328,7 @@ namespace osu.Game.Screens.Edit { Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 40 }, + Padding = new MarginPadding { Top = 40, Bottom = 50 }, Child = screenContainer = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 75b86650af..2204fabf57 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -104,10 +104,11 @@ namespace osu.Game.Screens.Edit.Timing // child items valid coordinates from the start, so ballpark something similar // using estimated row height. var row = Items.FlowingChildren.SingleOrDefault(item => item.Row.Equals(val.NewValue)); + if (row == null) return; - float minPos = Items.GetLayoutPosition(row) * row_height; + float minPos = row.Y; float maxPos = minPos + row_height; if (minPos < Scroll.Current) From 76d016df348025562fe028a249945d072a90b1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 11:31:16 +0200 Subject: [PATCH 226/670] Fix code inspection --- osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs index 508877859c..0ba67c0bb0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneJudgementContainer.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneJudgementContainer : OsuTestScene + public partial class TestSceneJudgementContainer : OsuTestScene { private JudgementContainer judgementContainer = null!; From f1325386f071adcf60b5666c2638e16cf07de671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jul 2024 18:32:54 +0900 Subject: [PATCH 227/670] Fix summary timeline timing points having x position applied twice --- .../Components/Timelines/Summary/Parts/GroupVisualisation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index e01900b129..b872c3725c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts case TimingControlPoint: AddInternal(new ControlPointVisualisation(point) { + // importantly, override the x position being set since we do that above. + X = 0, Y = -0.4f, }); break; From ae5b0aa54b463ace43b0a95e02576b33d27f651d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jul 2024 19:59:13 +0900 Subject: [PATCH 228/670] Fix BackgroundDataStoreProcessor test failure --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index e960995c45..70b6e32363 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Database Ruleset = r.All().First(), }) { - TotalScoreVersion = 30000002, + TotalScoreVersion = 30000013, IsLegacyScore = true, }); }); @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); - AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000013)); } [Test] From 6db135279fa27cf8c2abfac1c6bb27a2688a1269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:03:33 +0200 Subject: [PATCH 229/670] Restore test coverage of original fail case --- .../Database/BackgroundDataStoreProcessorTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 70b6e32363..65a8bcd3c2 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -157,8 +157,9 @@ namespace osu.Game.Tests.Database AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); } - [Test] - public void TestScoreUpgradeFailed() + [TestCase(30000002)] + [TestCase(30000013)] + public void TestScoreUpgradeFailed(int scoreVersion) { ScoreInfo scoreInfo = null!; @@ -172,7 +173,7 @@ namespace osu.Game.Tests.Database Ruleset = r.All().First(), }) { - TotalScoreVersion = 30000013, + TotalScoreVersion = scoreVersion, IsLegacyScore = true, }); }); @@ -181,7 +182,7 @@ namespace osu.Game.Tests.Database AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); - AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000013)); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion)); } [Test] From 53b6f9e3854db7933ec06a28164078ddc566fcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:01:33 +0200 Subject: [PATCH 230/670] Fix test not waiting properly for background processing to complete --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 65a8bcd3c2..f9f9fa2622 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -179,7 +179,9 @@ namespace osu.Game.Tests.Database }); }); - AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + TestBackgroundDataStoreProcessor processor = null!; + AddStep("Run background processor", () => Add(processor = new TestBackgroundDataStoreProcessor())); + AddUntilStep("Wait for completion", () => processor.Completed); AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(scoreVersion)); From 7ba1f142e573f8ca8173a8f64f139b4fd240be52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 16 Jul 2024 14:01:50 +0200 Subject: [PATCH 231/670] Fix rank upgrade path upgrading scores that failed background reprocessing earlier --- osu.Game/Database/BackgroundDataStoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/BackgroundDataStoreProcessor.cs b/osu.Game/Database/BackgroundDataStoreProcessor.cs index 7074c89b84..16ff766ea4 100644 --- a/osu.Game/Database/BackgroundDataStoreProcessor.cs +++ b/osu.Game/Database/BackgroundDataStoreProcessor.cs @@ -389,7 +389,7 @@ namespace osu.Game.Database HashSet scoreIds = realmAccess.Run(r => new HashSet( r.All() - .Where(s => s.TotalScoreVersion < 30000013) // last total score version with a significant change to ranks + .Where(s => s.TotalScoreVersion < 30000013 && !s.BackgroundReprocessingFailed) // last total score version with a significant change to ranks .AsEnumerable() // must be done after materialisation, as realm doesn't support // filtering on nested property predicates or projection via `.Select()` From 4c1f902969f82ecc4e974e869110281601079d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 11:46:17 +0200 Subject: [PATCH 232/670] Do not allow working beatmap to switch to protected beatmap in song select Principal fix to https://github.com/ppy/osu/issues/28880. --- osu.Game/Screens/Select/SongSelect.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ecf8210002..14c4a34d14 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -505,6 +505,13 @@ namespace osu.Game.Screens.Select var beatmap = e?.NewValue ?? Beatmap.Value; if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; + if (beatmap.BeatmapSetInfo.Protected && e != null) + { + Logger.Log($"Denying working beatmap switch to protected beatmap {beatmap}"); + Beatmap.Value = e.OldValue; + return; + } + Logger.Log($"Song select working beatmap updated to {beatmap}"); if (!Carousel.SelectBeatmap(beatmap.BeatmapInfo, false)) From 1ffc34b6518f55079728cd61aa47c5cab2fbc4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 11:46:59 +0200 Subject: [PATCH 233/670] Do not show protected beatmaps in playlist overlay Secondary fix to https://github.com/ppy/osu/issues/28880. --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 2d03a4a26d..b49c794aa3 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Music { base.LoadComplete(); - beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); + beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending && !s.Protected), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); From e4ff6b5c8b5f2a384f636e26f917c9f1e529ff2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 12:02:42 +0200 Subject: [PATCH 234/670] Add flags allowing excluding protected beatmaps from consideration in music controller This means that the protected beatmap can not be skipped forward/back to. Incidentally closes https://github.com/ppy/osu/issues/23199. --- osu.Game/Overlays/MusicController.cs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 116e60a014..b6553779bc 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays return; Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}"); - NextTrack(); + NextTrack(allowProtectedTracks: true); } else if (!IsPlaying) { @@ -207,9 +207,10 @@ namespace osu.Game.Overlays /// Play the previous track or restart the current track if it's current time below . /// /// Invoked when the operation has been performed successfully. - public void PreviousTrack(Action? onSuccess = null) => Schedule(() => + /// Whether to include beatmap sets when navigating. + public void PreviousTrack(Action? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() => { - PreviousTrackResult res = prev(); + PreviousTrackResult res = prev(allowProtectedTracks); if (res != PreviousTrackResult.None) onSuccess?.Invoke(res); }); @@ -217,8 +218,9 @@ namespace osu.Game.Overlays /// /// Play the previous track or restart the current track if it's current time below . /// + /// Whether to include beatmap sets when navigating. /// The that indicate the decided action. - private PreviousTrackResult prev() + private PreviousTrackResult prev(bool allowProtectedTracks) { if (beatmap.Disabled || !AllowTrackControl.Value) return PreviousTrackResult.None; @@ -233,8 +235,8 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Prev; - var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault() - ?? getBeatmapSets().LastOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().TakeWhile(i => !i.Equals(current?.BeatmapSetInfo)).LastOrDefault(s => !s.Protected || allowProtectedTracks) + ?? getBeatmapSets().AsEnumerable().LastOrDefault(s => !s.Protected || allowProtectedTracks); if (playableSet != null) { @@ -250,10 +252,11 @@ namespace osu.Game.Overlays /// Play the next random or playlist track. /// /// Invoked when the operation has been performed successfully. + /// Whether to include beatmap sets when navigating. /// A of the operation. - public void NextTrack(Action? onSuccess = null) => Schedule(() => + public void NextTrack(Action? onSuccess = null, bool allowProtectedTracks = false) => Schedule(() => { - bool res = next(); + bool res = next(allowProtectedTracks); if (res) onSuccess?.Invoke(); }); @@ -306,15 +309,15 @@ namespace osu.Game.Overlays Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore); } - private bool next() + private bool next(bool allowProtectedTracks) { if (beatmap.Disabled || !AllowTrackControl.Value) return false; queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo)).ElementAtOrDefault(1) - ?? getBeatmapSets().FirstOrDefault(); + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) && (!i.Protected || allowProtectedTracks)).ElementAtOrDefault(1) + ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); @@ -432,7 +435,7 @@ namespace osu.Game.Overlays private void onTrackCompleted() { if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value) - NextTrack(); + NextTrack(allowProtectedTracks: true); } private bool applyModTrackAdjustments; From c4141fff07a4378a0dc8c8b94fd4f64715367511 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 14:47:15 +0300 Subject: [PATCH 235/670] Fix storyboard sprites leaving gaps on edges when resolving from an atlas --- osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index c5d70ddecc..e25c915d8b 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -100,14 +100,15 @@ namespace osu.Game.Storyboards.Drawables skinSourceChanged(); } else - Texture = textureStore.Get(Sprite.Path); + Texture = textureStore.Get(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge); Sprite.ApplyTransforms(this); } private void skinSourceChanged() { - Texture = skin.GetTexture(Sprite.Path) ?? textureStore.Get(Sprite.Path); + Texture = skin.GetTexture(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge) ?? + textureStore.Get(Sprite.Path, WrapMode.ClampToEdge, WrapMode.ClampToEdge); // Setting texture will only update the size if it's zero. // So let's force an explicit update. From 3006bae0d8ea9d42ed887862dcb6e56e0b9be081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 14:11:35 +0200 Subject: [PATCH 236/670] Send client-generated session GUID for identification purposes This is the first half of a change that *may* fix https://github.com/ppy/osu/issues/26338 (it definitely fixes *one case* where the issue happens, but I'm not sure if it will cover all of them). As described in the issue thread, using the `jti` claim from the JWT used for authorisation seemed like a decent idea. However, upon closer inspection the scheme falls over badly in a specific scenario where: 1. A client instance connects to spectator server using JWT A. 2. At some point, JWT A expires, and is silently rotated by the game in exchange for JWT B. The spectator server knows nothing of this, and continues to only track JWT A, including the old `jti` claim in said JWT. 3. At some later point, the client's connection to one of the spectator server hubs drops out. A reconnection is automatically attempted, *but* it is attempted using JWT B. The spectator server was not aware of JWT B until now, and said JWT has a different `jti` claim than the old one, so to the spectator server, it looks like a completely different client connecting, which boots the user out of their account. This PR adds a per-session GUID which is sent in a HTTP header on every connection attempt to spectator server. This GUID will be used instead of the `jti` claim in JWTs as a persistent identifier of a single user's single lazer session, which bypasses the failure scenario described above. I don't think any stronger primitive than this is required. As far as I can tell this is as strong a protection as the JWT was (which is to say, not *very* strong), and doing this removes a lot of weird complexity that would be otherwise incurred by attempting to have client ferry all of its newly issued JWTs to the server so that it can be aware of them. --- osu.Game/Online/API/APIAccess.cs | 2 ++ osu.Game/Online/API/DummyAPIAccess.cs | 2 ++ osu.Game/Online/API/IAPIProvider.cs | 6 ++++++ osu.Game/Online/HubClientConnector.cs | 8 ++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 923f841bd8..0cf344ecaf 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -164,6 +164,8 @@ namespace osu.Game.Online.API public string AccessToken => authentication.RequestAccessToken(); + public Guid SessionIdentifier { get; } = Guid.NewGuid(); + /// /// Number of consecutive requests which failed due to network issues. /// diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 960941fc05..0af76537cd 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -39,6 +39,8 @@ namespace osu.Game.Online.API public string AccessToken => "token"; + public Guid SessionIdentifier { get; } = Guid.NewGuid(); + /// public bool IsLoggedIn => State.Value > APIState.Offline; diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 7b95b68ec3..d8194dc32b 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -44,6 +44,12 @@ namespace osu.Game.Online.API /// string AccessToken { get; } + /// + /// Used as an identifier of a single local lazer session. + /// Sent across the wire for the purposes of concurrency control to spectator server. + /// + Guid SessionIdentifier { get; } + /// /// Returns whether the local user is logged in. /// diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 9d414deade..dc9ed7cc2e 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -19,6 +19,9 @@ namespace osu.Game.Online { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; + public const string VERSION_HASH_HEADER = @"OsuVersionHash"; + public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID"; + /// /// Invoked whenever a new hub connection is built, to configure it before it's started. /// @@ -68,8 +71,9 @@ namespace osu.Game.Online options.Proxy.Credentials = CredentialCache.DefaultCredentials; } - options.Headers.Add("Authorization", $"Bearer {API.AccessToken}"); - options.Headers.Add("OsuVersionHash", versionHash); + options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}"); + options.Headers.Add(VERSION_HASH_HEADER, versionHash); + options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString()); }); if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack) From 2a601ce9617d0ef55bcb36b4de4e87278179b72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 17 Jul 2024 16:21:33 +0200 Subject: [PATCH 237/670] Also send version hash header under more accepted convention of name --- osu.Game/Online/HubClientConnector.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index dc9ed7cc2e..9288a32052 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online { public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; - public const string VERSION_HASH_HEADER = @"OsuVersionHash"; + public const string VERSION_HASH_HEADER = @"X-Osu-Version-Hash"; public const string CLIENT_SESSION_ID_HEADER = @"X-Client-Session-ID"; /// @@ -72,6 +72,8 @@ namespace osu.Game.Online } options.Headers.Add(@"Authorization", @$"Bearer {API.AccessToken}"); + // non-standard header name kept for backwards compatibility, can be removed after server side has migrated to `VERSION_HASH_HEADER` + options.Headers.Add(@"OsuVersionHash", versionHash); options.Headers.Add(VERSION_HASH_HEADER, versionHash); options.Headers.Add(CLIENT_SESSION_ID_HEADER, API.SessionIdentifier.ToString()); }); From 102da0f98c783fecb55736c574ee14e639fa9b6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 23:58:38 +0300 Subject: [PATCH 238/670] Remove incorrect `[CanBeNull]` attribute --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 1c07b38667..a2836476c5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -202,7 +202,6 @@ namespace osu.Game.Online.API.Requests.Responses public string PlayMode; [JsonProperty(@"profile_hue")] - [CanBeNull] public int? ProfileHue; [JsonProperty(@"profile_order")] From 4eb4d35e2f4c5b6edca8c10692165a22915195af Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 17 Jul 2024 23:58:47 +0300 Subject: [PATCH 239/670] Make `UpdateColours` method protected --- osu.Game/Overlays/FullscreenOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs index 2a09147c76..c2ecb55814 100644 --- a/osu.Game/Overlays/FullscreenOverlay.cs +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -100,7 +100,10 @@ namespace osu.Game.Overlays } } - public void UpdateColours() + /// + /// Updates the colours of the background and the top waves with the latest colour shades provided by . + /// + protected void UpdateColours() { Waves.FirstWaveColour = ColourProvider.Light4; Waves.SecondWaveColour = ColourProvider.Light3; From d61a72b8fbda78a475428549090e90ee9e840a97 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 00:05:44 +0300 Subject: [PATCH 240/670] Explicitly define `Hue` rather than implicitly provide it by enum value --- osu.Game/Overlays/OverlayColourProvider.cs | 42 +++++++-------- osu.Game/Overlays/OverlayColourScheme.cs | 60 ++++++++++++++++++++++ osu.Game/Overlays/UserProfileOverlay.cs | 16 +++--- osu.Game/Screens/Footer/ScreenFooter.cs | 8 +-- 4 files changed, 91 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Overlays/OverlayColourScheme.cs diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 9613bc2857..9f5583cf73 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -7,11 +7,19 @@ namespace osu.Game.Overlays { public class OverlayColourProvider { - public OverlayColourScheme ColourScheme { get; private set; } + /// + /// The hue degree associated with the colour shades provided by this . + /// + public int Hue { get; private set; } public OverlayColourProvider(OverlayColourScheme colourScheme) + : this(colourScheme.GetHue()) { - ColourScheme = colourScheme; + } + + public OverlayColourProvider(int hue) + { + Hue = hue; } // Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`. @@ -46,31 +54,19 @@ namespace osu.Game.Overlays public Color4 Background6 => getColour(0.1f, 0.1f); /// - /// Changes the value of to a different colour scheme. + /// Changes the to a different degree. /// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually. /// /// The proposed colour scheme. - public void ChangeColourScheme(OverlayColourScheme colourScheme) - { - ColourScheme = colourScheme; - } + public void ChangeColourScheme(OverlayColourScheme colourScheme) => ChangeColourScheme(colourScheme.GetHue()); - private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(getBaseHue(ColourScheme), saturation, lightness); + /// + /// Changes the to a different degree. + /// Note that this does not trigger any kind of signal to any drawable that received colours from here, all drawables need to be updated manually. + /// + /// The proposed hue degree. + public void ChangeColourScheme(int hue) => Hue = hue; - private static float getBaseHue(OverlayColourScheme colourScheme) => (int)colourScheme / 360f; - } - - // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 - public enum OverlayColourScheme - { - Red = 0, - Orange = 45, - Lime = 90, - Green = 125, - Aquamarine = 160, - Blue = 200, - Purple = 255, - Plum = 320, - Pink = 333, + private Color4 getColour(float saturation, float lightness) => Framework.Graphics.Colour4.FromHSL(Hue / 360f, saturation, lightness); } } diff --git a/osu.Game/Overlays/OverlayColourScheme.cs b/osu.Game/Overlays/OverlayColourScheme.cs new file mode 100644 index 0000000000..0126f9060f --- /dev/null +++ b/osu.Game/Overlays/OverlayColourScheme.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Overlays +{ + public enum OverlayColourScheme + { + Red, + Orange, + Lime, + Green, + Aquamarine, + Blue, + Purple, + Plum, + Pink, + } + + public static class OverlayColourSchemeExtensions + { + public static int GetHue(this OverlayColourScheme colourScheme) + { + // See https://github.com/ppy/osu-web/blob/5a536d217a21582aad999db50a981003d3ad5659/app/helpers.php#L1620-L1628 + switch (colourScheme) + { + default: + throw new ArgumentOutOfRangeException(nameof(colourScheme)); + + case OverlayColourScheme.Red: + return 0; + + case OverlayColourScheme.Orange: + return 45; + + case OverlayColourScheme.Lime: + return 90; + + case OverlayColourScheme.Green: + return 125; + + case OverlayColourScheme.Aquamarine: + return 160; + + case OverlayColourScheme.Blue: + return 200; + + case OverlayColourScheme.Purple: + return 255; + + case OverlayColourScheme.Plum: + return 320; + + case OverlayColourScheme.Pink: + return 333; + } + } + } +} diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 8c750b5d83..815f4b545f 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -118,7 +118,7 @@ namespace osu.Game.Overlays } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink, forceContentRecreation: true); + setupBaseContent(OverlayColourScheme.Pink.GetHue(), forceContentRecreation: true); if (API.State.Value != APIState.Offline) { @@ -135,9 +135,9 @@ namespace osu.Game.Overlays Debug.Assert(sections != null && sectionsContainer != null && tabs != null); // reuse header and content if same colour scheme, otherwise recreate both. - var profileScheme = (OverlayColourScheme?)loadedUser.ProfileHue ?? OverlayColourScheme.Pink; - if (profileScheme != ColourProvider.ColourScheme) - setupBaseContent(profileScheme, forceContentRecreation: false); + int profileHue = loadedUser.ProfileHue ?? OverlayColourScheme.Pink.GetHue(); + if (profileHue != ColourProvider.Hue) + setupBaseContent(profileHue, forceContentRecreation: false); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -163,12 +163,12 @@ namespace osu.Game.Overlays loadingLayer.Hide(); } - private void setupBaseContent(OverlayColourScheme colourScheme, bool forceContentRecreation) + private void setupBaseContent(int hue, bool forceContentRecreation) { - var previousColourScheme = ColourProvider.ColourScheme; - ColourProvider.ChangeColourScheme(colourScheme); + int previousHue = ColourProvider.Hue; + ColourProvider.ChangeColourScheme(hue); - if (colourScheme != previousColourScheme) + if (hue != previousHue) { RecreateHeader(); UpdateColours(); diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 6a1efcf87a..ea32507ca0 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Footer var targetPosition = targetButton?.ToSpaceOfOtherDrawable(targetButton.LayoutRectangle.TopRight, this) ?? fallbackPosition; - updateColourScheme(overlay.ColourProvider.ColourScheme); + updateColourScheme(overlay.ColourProvider.Hue); footerContent = overlay.CreateFooterContent(); @@ -256,16 +256,16 @@ namespace osu.Game.Screens.Footer temporarilyHiddenButtons.Clear(); - updateColourScheme(OverlayColourScheme.Aquamarine); + updateColourScheme(OverlayColourScheme.Aquamarine.GetHue()); contentContainer.Delay(timeUntilRun).Expire(); contentContainer = null; activeOverlay = null; } - private void updateColourScheme(OverlayColourScheme colourScheme) + private void updateColourScheme(int hue) { - colourProvider.ChangeColourScheme(colourScheme); + colourProvider.ChangeColourScheme(hue); background.FadeColour(colourProvider.Background5, 150, Easing.OutQuint); From 5317086171ee7da13154b434620b856994d013a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 00:26:37 +0300 Subject: [PATCH 241/670] Split content recreation methods --- osu.Game/Overlays/UserProfileOverlay.cs | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 815f4b545f..ac1fc44cd6 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -118,7 +118,8 @@ namespace osu.Game.Overlays } : Array.Empty(); - setupBaseContent(OverlayColourScheme.Pink.GetHue(), forceContentRecreation: true); + changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + recreateBaseContent(); if (API.State.Value != APIState.Offline) { @@ -136,8 +137,9 @@ namespace osu.Game.Overlays // reuse header and content if same colour scheme, otherwise recreate both. int profileHue = loadedUser.ProfileHue ?? OverlayColourScheme.Pink.GetHue(); - if (profileHue != ColourProvider.Hue) - setupBaseContent(profileHue, forceContentRecreation: false); + + if (changeOverlayColours(profileHue)) + recreateBaseContent(); var actualRuleset = rulesets.GetRuleset(userRuleset?.ShortName ?? loadedUser.PlayMode).AsNonNull(); @@ -163,19 +165,8 @@ namespace osu.Game.Overlays loadingLayer.Hide(); } - private void setupBaseContent(int hue, bool forceContentRecreation) + private void recreateBaseContent() { - int previousHue = ColourProvider.Hue; - ColourProvider.ChangeColourScheme(hue); - - if (hue != previousHue) - { - RecreateHeader(); - UpdateColours(); - } - else if (!forceContentRecreation) - return; - Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -224,6 +215,18 @@ namespace osu.Game.Overlays }; } + private bool changeOverlayColours(int hue) + { + if (hue == ColourProvider.Hue) + return false; + + ColourProvider.ChangeColourScheme(hue); + + RecreateHeader(); + UpdateColours(); + return true; + } + private partial class ProfileSectionTabControl : OsuTabControl { public ProfileSectionTabControl() From 7a394350170d804d2744706cf0d03b815f47a9cf Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 18 Jul 2024 01:11:39 +0300 Subject: [PATCH 242/670] Fix intermitent test failure in `TestSceneArgonHealthDisplay` --- osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs index 5d2921107e..319efee1a7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneArgonHealthDisplay.cs @@ -99,6 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay Scheduler.AddDelayed(applyMiss, 500 + 30); }); + AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks); } [Test] @@ -120,6 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay } } }); + AddUntilStep("wait for sequence", () => !Scheduler.HasPendingTasks); } [Test] From 1906c2f72537fde9386f03313afa3e95ba9b1663 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 15:57:57 +0900 Subject: [PATCH 243/670] Fix TestTouchScreenDetectionAtSongSelect test failure https://github.com/ppy/osu/actions/runs/9985890747/job/27597501295 In this case, the settings overlay is taking a very long time to load (on a background thread), and pops in when it finishes loading because it's been requested to open. The opens the settings overlay, closes it (by pressing escape, this does not actually close it because it's not loaded yet), and then enters song select by pressing 'P' 3 times. The settings overlay finishes loading at just the right opportune moment to eat one of the 'P' key presses. --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index e81c6d2e86..3ae1d9786d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -952,6 +952,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestTouchScreenDetectionAtSongSelect() { + AddUntilStep("wait for settings", () => Game.Settings.IsLoaded); + AddStep("touch logo", () => { var button = Game.ChildrenOfType().Single(); From 7bb680a8a445cd7a87ea206627f925a08dd0a337 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 16:01:09 +0900 Subject: [PATCH 244/670] Raise workflow timeout time https://github.com/ppy/osu/actions/runs/9985890747/job/27597500883 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ea4654563..dc1cb6c186 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,7 +67,7 @@ jobs: - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] - timeout-minutes: 60 + timeout-minutes: 120 steps: - name: Checkout uses: actions/checkout@v4 From f3cd3d7d3b8c7d56746b6d93a103d71ef6de991e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 16:22:39 +0900 Subject: [PATCH 245/670] Fix TestAllSamplesStopDuringSeek test failure https://github.com/smoogipoo/osu/actions/runs/9986761756/job/27599851263 This is a bit of a workaround, likely timing related. I don't foresee an until step in this case to cause false-passes. --- .../Visual/Gameplay/TestSceneGameplaySamplePlayback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs index ad3fe7cb7e..21c83d521c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay return true; }); - AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); + AddUntilStep("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value); // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. // the important thing is that at least one started, and that sample has since stopped. From 00ed7a7a2f19f9770c0772bc9af0093d0dfd07c5 Mon Sep 17 00:00:00 2001 From: Nathan Du Date: Thu, 18 Jul 2024 16:08:30 +0800 Subject: [PATCH 246/670] Fix hold note light lingering with No Release Turns out endHold() is not called in the Tail.IsHit branch of the hold notes' CheckForResult method. --- .../Objects/Drawables/DrawableHoldNote.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2b55e81788..9c56f0473c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -268,11 +268,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables ApplyMaxResult(); else MissForcefully(); - } - // Make sure that the hold note is fully judged by giving the body a judgement. - if (Tail.AllJudged && !Body.AllJudged) - Body.TriggerResult(Tail.IsHit); + // Make sure that the hold note is fully judged by giving the body a judgement. + if (!Body.AllJudged) + Body.TriggerResult(Tail.IsHit); + + // Important that this is always called when a result is applied. + endHold(); + } } public override void MissForcefully() From 33a81d818107b80c73c4dd7ad483a9c59abb474a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 18:34:08 +0900 Subject: [PATCH 247/670] Use constraint to improve assertion message --- .../Visual/Navigation/TestSceneBeatmapEditorNavigation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index b5dfa9a87f..99d1ff93c5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit()); AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - AddAssert("Check no new beatmaps were made", () => allBeatmapSets().SequenceEqual(beatmapSets)); + AddAssert("Check no new beatmaps were made", allBeatmapSets, () => Is.EquivalentTo(beatmapSets)); BeatmapSetInfo[] allBeatmapSets() => Game.Realm.Run(realm => realm.All().Where(x => !x.DeletePending).ToArray()); } From c9517aeebf0c9a726436d2d1776e7c69266c7df4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2024 18:37:07 +0900 Subject: [PATCH 248/670] Fix tab extension dropdown having dead non-clickable hover area Closes https://github.com/ppy/osu/issues/28899. --- osu.Game/Graphics/UserInterface/OsuTabDropdown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs index 6272f95510..5924ee005a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs @@ -123,7 +123,7 @@ namespace osu.Game.Graphics.UserInterface } }; - Padding = new MarginPadding { Left = 5, Right = 5 }; + Margin = new MarginPadding { Left = 5, Right = 5 }; } protected override bool OnHover(HoverEvent e) From 70985d3b2234d746275bc9e1f45891cc41ea0ab9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jul 2024 19:01:52 +0900 Subject: [PATCH 249/670] Remove margin completely --- osu.Game/Graphics/UserInterface/OsuTabDropdown.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs index 5924ee005a..7a17be57a8 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabDropdown.cs @@ -122,8 +122,6 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.Centre, } }; - - Margin = new MarginPadding { Left = 5, Right = 5 }; } protected override bool OnHover(HoverEvent e) From a7e110f6693beca6f6e6a20efb69a6913d58550e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 19:07:02 +0900 Subject: [PATCH 250/670] Don't rely on single-use properties --- .../Objects/Drawables/DrawableOsuJudgement.cs | 57 ++++++++++--------- .../Objects/Drawables/SkinnableLighting.cs | 18 +++--- .../Rulesets/Judgements/DrawableJudgement.cs | 6 +- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 0630ecfbb5..8b3fcb23cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -5,19 +5,23 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableOsuJudgement : DrawableJudgement { + internal Color4 AccentColour { get; private set; } + internal SkinnableLighting Lighting { get; private set; } = null!; [Resolved] private OsuConfigManager config { get; set; } = null!; - private bool positionTransferred; + private Vector2 screenSpacePosition; [BackgroundDependencyLoader] private void load() @@ -32,37 +36,36 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }); } + public override void Apply(JudgementResult result, DrawableHitObject? judgedObject) + { + base.Apply(result, judgedObject); + + if (judgedObject is not DrawableOsuHitObject osuObject) + return; + + AccentColour = osuObject.AccentColour.Value; + + switch (osuObject) + { + case DrawableSlider slider: + screenSpacePosition = slider.TailCircle.ToScreenSpace(slider.TailCircle.OriginPosition); + break; + + default: + screenSpacePosition = osuObject.ToScreenSpace(osuObject.OriginPosition); + break; + } + + Scale = new Vector2(osuObject.HitObject.Scale); + } + protected override void PrepareForUse() { base.PrepareForUse(); Lighting.ResetAnimation(); - Lighting.SetColourFrom(JudgedObject, Result); - - positionTransferred = false; - } - - protected override void Update() - { - base.Update(); - - if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) - { - switch (osuObject) - { - case DrawableSlider slider: - Position = slider.TailCircle.ToSpaceOfOtherDrawable(slider.TailCircle.OriginPosition, Parent!); - break; - - default: - Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); - break; - } - - positionTransferred = true; - - Scale = new Vector2(osuObject.HitObject.Scale); - } + Lighting.SetColourFrom(this, Result); + Position = Parent!.ToLocalSpace(screenSpacePosition); } protected override void ApplyHitAnimations() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs index b39b9c4c54..3776201626 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SkinnableLighting.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Skinning; @@ -12,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { internal partial class SkinnableLighting : SkinnableSprite { - private DrawableHitObject targetObject; - private JudgementResult targetResult; + private DrawableOsuJudgement? targetJudgement; + private JudgementResult? targetResult; public SkinnableLighting() : base("lighting") @@ -29,11 +27,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Updates the lighting colour from a given hitobject and result. /// - /// The that's been judged. - /// The that was judged with. - public void SetColourFrom(DrawableHitObject targetObject, JudgementResult targetResult) + /// The that's been judged. + /// The that was judged with. + public void SetColourFrom(DrawableOsuJudgement targetJudgement, JudgementResult? targetResult) { - this.targetObject = targetObject; + this.targetJudgement = targetJudgement; this.targetResult = targetResult; updateColour(); @@ -41,10 +39,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateColour() { - if (targetObject == null || targetResult == null) + if (targetJudgement == null || targetResult == null) Colour = Color4.White; else - Colour = targetResult.IsHit ? targetObject.AccentColour.Value : Color4.Transparent; + Colour = targetResult.IsHit ? targetJudgement.AccentColour : Color4.Transparent; } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 189be44033..bdeadfd201 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Judgements public JudgementResult? Result { get; private set; } - public DrawableHitObject? JudgedObject { get; private set; } - public HitObject? JudgedHitObject { get; private set; } public override bool RemoveCompletedTransforms => false; @@ -97,10 +95,9 @@ namespace osu.Game.Rulesets.Judgements /// /// The applicable judgement. /// The drawable object. - public void Apply(JudgementResult result, DrawableHitObject? judgedObject) + public virtual void Apply(JudgementResult result, DrawableHitObject? judgedObject) { Result = result; - JudgedObject = judgedObject; JudgedHitObject = judgedObject?.HitObject; } @@ -108,7 +105,6 @@ namespace osu.Game.Rulesets.Judgements { base.FreeAfterUse(); - JudgedObject = null; JudgedHitObject = null; } From 3f4e56be3ce3532b6c7b75f530a1a2b7c45a99ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 18 Jul 2024 20:53:59 +0900 Subject: [PATCH 251/670] Fix TestPostAsOwner test failure https://github.com/smoogipoo/osu/actions/runs/9990112749/job/27610257309 Comments are loaded asynchronously, both from the initial request and the following message-post request. By sheer timing luck, these could be out of order and the assertion on the posted message could fail. --- osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index fd3552f675..acc3c9b8b4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -157,6 +157,7 @@ namespace osu.Game.Tests.Visual.Online { setUpCommentsResponse(getExampleComments()); AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType().Any()); setUpPostResponse(); AddStep("enter text", () => editorTextBox.Current.Value = "comm"); @@ -175,6 +176,7 @@ namespace osu.Game.Tests.Visual.Online { setUpCommentsResponse(getExampleComments()); AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("comments shown", () => commentsContainer.ChildrenOfType().Any()); setUpPostResponse(true); AddStep("enter text", () => editorTextBox.Current.Value = "comm"); From a570949459b3e66fab91fd87834df01547ace2b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:00:44 +0300 Subject: [PATCH 252/670] Fix selection box initialy visible despite no items selected --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index b68d5cd540..16d11ccd1a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -85,10 +85,7 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBox = CreateSelectionBox(), }); - SelectedItems.CollectionChanged += (_, _) => - { - Scheduler.AddOnce(updateVisibility); - }; + SelectedItems.BindCollectionChanged((_, _) => Scheduler.AddOnce(updateVisibility), true); } public SelectionBox CreateSelectionBox() From dd2454ba10532798d9e5c59136123507efd098a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:17:50 +0300 Subject: [PATCH 253/670] Disable trailing comma inspections entirely --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 25bbc4beb5..0c52f8d82a 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -20,6 +20,7 @@ WARNING WARNING True + DO_NOT_SHOW WARNING WARNING HINT From 2ad8eeb918b7129f13df5f985c6f0739ab8ff5d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 03:25:12 +0300 Subject: [PATCH 254/670] Fix beatmap attributes display in mod select recreating star difficulty bindable every setting change --- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 1f4e007f47..2670c20d26 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -108,8 +108,6 @@ namespace osu.Game.Overlays.Mods updateValues(); }, true); - BeatmapInfo.BindValueChanged(_ => updateValues()); - Collapsed.BindValueChanged(_ => { // Only start autosize animations on first collapse toggle. This avoids an ugly initial presentation. @@ -120,12 +118,32 @@ namespace osu.Game.Overlays.Mods GameRuleset = game.Ruleset.GetBoundCopy(); GameRuleset.BindValueChanged(_ => updateValues()); - BeatmapInfo.BindValueChanged(_ => updateValues()); + BeatmapInfo.BindValueChanged(_ => + { + updateStarDifficultyBindable(); + updateValues(); + }, true); - updateValues(); updateCollapsedState(); } + private void updateStarDifficultyBindable() + { + cancellationSource?.Cancel(); + + if (BeatmapInfo.Value == null) + return; + + starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token); + starDifficulty.BindValueChanged(s => + { + starRatingDisplay.Current.Value = s.NewValue ?? default; + + if (!starRatingDisplay.IsPresent) + starRatingDisplay.FinishTransforms(true); + }); + } + protected override bool OnHover(HoverEvent e) { startAnimating(); @@ -154,17 +172,6 @@ namespace osu.Game.Overlays.Mods if (BeatmapInfo.Value == null) return; - cancellationSource?.Cancel(); - - starDifficulty = difficultyCache.GetBindableDifficulty(BeatmapInfo.Value, (cancellationSource = new CancellationTokenSource()).Token); - starDifficulty.BindValueChanged(s => - { - starRatingDisplay.Current.Value = s.NewValue ?? default; - - if (!starRatingDisplay.IsPresent) - starRatingDisplay.FinishTransforms(true); - }); - double rate = ModUtils.CalculateRateWithMods(Mods.Value); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); From 73edb324403c4ea1e1716547e2756f6fd5df001a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:30:55 +0200 Subject: [PATCH 255/670] Add failing test coverage --- .../Visual/Menus/TestSceneMusicActionHandling.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index f17433244b..9936b24a06 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -62,14 +62,22 @@ namespace osu.Game.Tests.Visual.Menus AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000); AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev)); - AddAssert("track changed to previous", () => + AddUntilStep("track changed to previous", () => trackChangeQueue.Count == 1 && trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev); AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); - AddAssert("track changed to next", () => + AddUntilStep("track changed to next", () => trackChangeQueue.Count == 1 && - trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next); + trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next); + + AddUntilStep("wait until track switches", () => trackChangeQueue.Count == 2); + + AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext)); + AddUntilStep("track changed to next", () => + trackChangeQueue.Count == 3 && + trackChangeQueue.Peek().changeDirection == TrackChangeDirection.Next); + AddAssert("track actually changed", () => !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo)); } } } From 9fe6354afc5517bb4527397777e2299e2cf8e7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:32:29 +0200 Subject: [PATCH 256/670] Fix backwards conditional --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index b6553779bc..d9bb92b4b7 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -316,7 +316,7 @@ namespace osu.Game.Overlays queuedDirection = TrackChangeDirection.Next; - var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) && (!i.Protected || allowProtectedTracks)).ElementAtOrDefault(1) + var playableSet = getBeatmapSets().AsEnumerable().SkipWhile(i => !i.Equals(current?.BeatmapSetInfo) || (i.Protected && !allowProtectedTracks)).ElementAtOrDefault(1) ?? getBeatmapSets().AsEnumerable().FirstOrDefault(i => !i.Protected || allowProtectedTracks); var playableBeatmap = playableSet?.Beatmaps.FirstOrDefault(); From 79cf644b8def9d767348a643d610933400c25c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 07:33:58 +0200 Subject: [PATCH 257/670] Enable NRT while we're here --- osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9936b24a06..03b3b94bd8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -34,7 +32,7 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestMusicNavigationActions() { - Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; + Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null!; // ensure we have at least two beatmaps available to identify the direction the music controller navigated to. AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()), 5); From d7ae9505b2c3fe826d769c64279468671de94d82 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 14:08:05 +0900 Subject: [PATCH 258/670] Fix TestCancelNavigationToEditor test failure https://github.com/ppy/osu/actions/runs/10002179087/job/27648253709 The editor could be pushed before the exit actually occurs. --- .../TestSceneBeatmapEditorNavigation.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 99d1ff93c5..5640682d06 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -1,12 +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 System; using System.IO; using System.Linq; +using System.Threading; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Screens; @@ -204,9 +208,13 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("Set current beatmap to default", () => Game.Beatmap.SetDefault()); - AddStep("Push editor loader", () => Game.ScreenStack.Push(new EditorLoader())); + DelayedLoadEditorLoader loader = null!; + AddStep("Push editor loader", () => Game.ScreenStack.Push(loader = new DelayedLoadEditorLoader())); AddUntilStep("Wait for loader current", () => Game.ScreenStack.CurrentScreen is EditorLoader); + AddUntilStep("wait for editor load start", () => loader.Editor != null); AddStep("Close editor while loading", () => Game.ScreenStack.CurrentScreen.Exit()); + AddStep("allow editor load", () => loader.AllowLoad.Set()); + AddUntilStep("wait for editor ready", () => loader.Editor!.LoadState >= LoadState.Ready); AddUntilStep("Wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddAssert("Check no new beatmaps were made", allBeatmapSets, () => Is.EquivalentTo(beatmapSets)); @@ -356,5 +364,33 @@ namespace osu.Game.Tests.Visual.Navigation private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; + + private partial class DelayedLoadEditorLoader : EditorLoader + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); + public Editor? Editor { get; private set; } + + protected override Editor CreateEditor() => Editor = new DelayedLoadEditor(this); + } + + private partial class DelayedLoadEditor : Editor + { + private readonly DelayedLoadEditorLoader loader; + + public DelayedLoadEditor(DelayedLoadEditorLoader loader) + : base(loader) + { + this.loader = loader; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + // Importantly, this occurs before base.load(). + if (!loader.AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); + + return base.CreateChildDependencies(parent); + } + } } } From 4dd225fdc8184f062292ff15c258076f29e0bfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jul 2024 08:26:53 +0200 Subject: [PATCH 259/670] Fix compose blueprint container not unsubscribing from event Closes https://github.com/ppy/osu/issues/28938. This is related to reloading the composer on timing point changes in scrolling rulesets. The lack of unsubscription from this would cause blueprints to be created for disposed composers via the `hitObjectAdded()` flow. The following line looks as if a sync load should be forced on a newly created placement blueprint: https://github.com/ppy/osu/blob/da4d37c4aded5e10d0a65ff44a08a886e3897e19/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs#L364 however, it is not the case if the parent (`placementBlueprintContainer`) is disposed, which it would be in this case. Therefore, the blueprint stays `NotLoaded` rather than `Ready`, therefore it never receives its DI dependencies, therefore it dies on an `EditorBeatmap` nullref. --- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index fc8bce4c96..f1294ccc3c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -9,6 +9,7 @@ using Humanizer; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -405,5 +406,13 @@ namespace osu.Game.Screens.Edit.Compose.Components CommitIfPlacementActive(); } } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (Beatmap.IsNotNull()) + Beatmap.HitObjectAdded -= hitObjectAdded; + } } } From 0560214d5b7fd9f71cb1abdd4f32d878c511373b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 15:15:49 +0900 Subject: [PATCH 260/670] Fix beatmap carousel performance regression with large databases --- osu.Game/Screens/Select/BeatmapCarousel.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 3f9e676068..c76dbf9502 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -278,13 +278,27 @@ namespace osu.Game.Screens.Select if (changes == null) { + // Usually we'd handle the initial load case here, but that's already done in load() as an optimisation. + // So the only thing we need to handle here is the edge case where realm blocks-resumes all operations. realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); + // Do a full two-way check on missing beatmaps. + // Let's assume that the worst that can happen is deletions or additions. setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); - loadBeatmapSets(sender); + foreach (Guid id in realmBeatmapSets) + { + if (!root.BeatmapSetsByID.ContainsKey(id)) + setsRequiringUpdate.Add(id); + } + + foreach (Guid id in root.BeatmapSetsByID.Keys) + { + if (!realmBeatmapSets.Contains(id)) + setsRequiringRemoval.Add(id); + } } else { From 0f29ed618a691e419926de8b5b95df53af2aba47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 17:39:43 +0900 Subject: [PATCH 261/670] Don't attempt to clear the carousel during realm blocking operation --- osu.Game/Screens/Select/BeatmapCarousel.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c76dbf9502..cd0d2eea2c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -278,12 +278,21 @@ namespace osu.Game.Screens.Select if (changes == null) { - // Usually we'd handle the initial load case here, but that's already done in load() as an optimisation. - // So the only thing we need to handle here is the edge case where realm blocks-resumes all operations. realmBeatmapSets.Clear(); realmBeatmapSets.AddRange(sender.Select(r => r.ID)); - // Do a full two-way check on missing beatmaps. + if (originalBeatmapSetsDetached.Count > 0 && sender.Count == 0) + { + // Usually we'd reset stuff here, but doing so triggers a silly flow which ends up deadlocking realm. + // Additionally, user should not be at song select when realm is blocking all operations in the first place. + // + // Note that due to the catch-up logic below, once operations are restored we will still be in a roughly + // correct state. The only things that this return will change is the carousel will not empty *during* the blocking + // operation. + return; + } + + // Do a full two-way check for missing (or incorrectly present) beatmaps. // Let's assume that the worst that can happen is deletions or additions. setsRequiringRemoval.Clear(); setsRequiringUpdate.Clear(); From 7a4758d8ccbf10e8118f4e71ff0075b4107b3c4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 17:49:13 +0900 Subject: [PATCH 262/670] Attempt to fix TestSelectableMouseHandling test failure https://github.com/ppy/osu/pull/28900/checks?check_run_id=27652166871 This is an attempt. Going frame-by-frame I noticed that there's one frame in which the text is loaded but the FillFlowContainer/GridContainer haven't properly validated so the text is not positioned correctly (it's overflowing the panel to the left). If the cursor is moved at this exact time, then it may not be properly positioned for the following assertion, even though it is _somewhere_ on the panel. If the above is the case, then this is a known o!f issue, but not a simple one to solve. I haven't reproed this locally. --- .../TestSceneDrawableRoomPlaylist.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index bd62a8b131..2ef56bd54e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -319,16 +320,17 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for load", () => playlist.ChildrenOfType().Any() && playlist.ChildrenOfType().First().DrawWidth > 0); - AddStep("move mouse to first item title", () => - { - var drawQuad = playlist.ChildrenOfType().First().ScreenSpaceDrawQuad; - var location = (drawQuad.TopLeft + drawQuad.BottomLeft) / 2 + new Vector2(drawQuad.Width * 0.2f, 0); - InputManager.MoveMouseTo(location); - }); + + AddStep("move mouse to first item title", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().First().ChildrenOfType().First())); AddAssert("first item title not hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.False); - AddStep("click left mouse", () => InputManager.Click(MouseButton.Left)); + + AddStep("click title", () => + { + InputManager.MoveMouseTo(playlist.ChildrenOfType().First().ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("first item selected", () => playlist.ChildrenOfType().First().IsSelectedItem, () => Is.True); - // implies being clickable. AddUntilStep("first item title hovered", () => playlist.ChildrenOfType().First().IsHovered, () => Is.True); AddStep("move mouse to second item results button", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(5))); From 5af39aad00878d577fb57fcd020ffd8340cfffe9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 19 Jul 2024 19:02:41 +0900 Subject: [PATCH 263/670] Add beatmap name to log string Makes it easy to compare this line versus the one in OsuGame.PresentBeatmap(). At the moment it's just GUID which is... not useful! --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 14c4a34d14..307043a312 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -609,7 +609,7 @@ namespace osu.Game.Screens.Select // clear pending task immediately to track any potential nested debounce operation. selectionChangedDebounce = null; - Logger.Log($"Song select updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); + Logger.Log($"Song select updating selection with beatmap: {beatmap} {beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ShortName ?? "null"}"); if (transferRulesetValue()) { From f11f01f9b70fb3548f8e86470ed27289a3c66560 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:16:37 +0900 Subject: [PATCH 264/670] Fix various visuals of playlist beatmap panels Supersedes https://github.com/ppy/osu/pull/28907. - Fix border being fat - Fix thumbnail not masking correctly - Fix background layer not being correctly fit to the panel - Dim the main background on hover - Minor tweaks to dimming --- .../Drawables/Cards/BeatmapCardThumbnail.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 234 ++++++++++-------- 2 files changed, 130 insertions(+), 106 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 7b668d7dc4..976f797760 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards bool shouldDim = Dimmed.Value || playButton.Playing.Value; playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.8f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(colourProvider.Background6.Opacity(shouldDim ? 0.6f : 0f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ab32ca2558..43ffaf947e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.OnlinePlay private const float icon_height = 34; + private const float border_thickness = 3; + /// /// Invoked when this item requests to be deleted. /// @@ -81,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay private IRulesetInfo ruleset; private Mod[] requiredMods = Array.Empty(); - private Container maskingContainer; + private Container borderContainer; private FillFlowContainer difficultyIconContainer; private LinkFlowContainer beatmapText; private LinkFlowContainer authorText; @@ -134,7 +136,7 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - maskingContainer.BorderColour = colours.Yellow; + borderContainer.BorderColour = colours.Yellow; ruleset = rulesets.GetRuleset(Item.RulesetID); var rulesetInstance = ruleset?.CreateInstance(); @@ -161,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay return; } - maskingContainer.BorderThickness = IsSelectedItem ? 5 : 0; + borderContainer.BorderThickness = IsSelectedItem ? border_thickness : 0; }, true); valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); @@ -278,8 +280,8 @@ namespace osu.Game.Screens.OnlinePlay { if (!valid.Value) { - maskingContainer.BorderThickness = 5; - maskingContainer.BorderColour = colours.Red; + borderContainer.BorderThickness = border_thickness; + borderContainer.BorderColour = colours.Red; } if (beatmap != null) @@ -291,12 +293,14 @@ namespace osu.Game.Screens.OnlinePlay Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Width = 60, + Masking = true, + CornerRadius = 10, RelativeSizeAxes = Axes.Y, Dimmed = { Value = IsHovered } }, new DifficultyIcon(beatmap, ruleset, requiredMods) { - Size = new Vector2(icon_height), + Size = new Vector2(24), TooltipType = DifficultyIconTooltipType.Extended, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -346,136 +350,153 @@ namespace osu.Game.Screens.OnlinePlay { Action fontParameters = s => s.Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold); - return maskingContainer = new Container + return new Container { RelativeSizeAxes = Axes.X, Height = HEIGHT, - Masking = true, - CornerRadius = 10, Children = new Drawable[] { - new Box // A transparent box that forces the border to be drawn if the panel background is opaque + new Container { RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - onScreenLoader, - panelBackground = new PanelBackground - { - RelativeSizeAxes = Axes.Both, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] + onScreenLoader, + panelBackground = new PanelBackground { - difficultyIconContainer = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4), - Margin = new MarginPadding { Right = 4 }, + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) }, - mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) + Content = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Drawable[] { - beatmapText = new LinkFlowContainer(fontParameters) + difficultyIconContainer = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). - // TODO: remove when text/link flow can support truncation with ellipsis natively. - Height = OsuFont.DEFAULT_FONT_SIZE, - Masking = true - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0), + Spacing = new Vector2(4), + Margin = new MarginPadding { Right = 4 }, + }, + mainFillFlow = new MainFlow(() => SelectedItem.Value == Model || !AllowSelection) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, -2), Children = new Drawable[] { + beatmapText = new LinkFlowContainer(fontParameters) + { + RelativeSizeAxes = Axes.X, + // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). + // TODO: remove when text/link flow can support truncation with ellipsis natively. + Height = OsuFont.DEFAULT_FONT_SIZE, + Masking = true + }, new FillFlowContainer { AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0), Children = new Drawable[] { - authorText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, - explicitContent = new ExplicitContentBeatmapBadge + new FillFlowContainer { - Alpha = 0f, + AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Top = 3f }, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10f, 0), + Children = new Drawable[] + { + authorText = new LinkFlowContainer(fontParameters) { AutoSizeAxes = Axes.Both }, + explicitContent = new ExplicitContentBeatmapBadge + { + Alpha = 0f, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Top = 3f }, + } + }, + }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Child = modDisplay = new ModDisplay + { + Scale = new Vector2(0.4f), + ExpansionMode = ExpansionMode.AlwaysExpanded, + Margin = new MarginPadding { Vertical = -6 }, + } } - }, - }, - new Container - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Child = modDisplay = new ModDisplay - { - Scale = new Vector2(0.4f), - ExpansionMode = ExpansionMode.AlwaysExpanded, - Margin = new MarginPadding { Vertical = -6 }, } } } - } + }, + buttonsFlow = new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Horizontal = 8 }, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + ChildrenEnumerable = createButtons().Select(button => button.With(b => + { + b.Anchor = Anchor.Centre; + b.Origin = Anchor.Centre; + })) + }, + ownerAvatar = new OwnerAvatar + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_height), + Margin = new MarginPadding { Right = 8 }, + Masking = true, + CornerRadius = 4, + Alpha = ShowItemOwner ? 1 : 0 + }, } - }, - buttonsFlow = new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Horizontal = 8 }, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - ChildrenEnumerable = createButtons().Select(button => button.With(b => - { - b.Anchor = Anchor.Centre; - b.Origin = Anchor.Centre; - })) - }, - ownerAvatar = new OwnerAvatar - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(icon_height), - Margin = new MarginPadding { Right = 8 }, - Masking = true, - CornerRadius = 4, - Alpha = ShowItemOwner ? 1 : 0 - }, - } - } + } + }, + }, }, - }, + borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box // A transparent box that forces the border to be drawn if the panel background is opaque + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + } + } + } }; } @@ -509,6 +530,8 @@ namespace osu.Game.Screens.OnlinePlay { if (thumbnail != null) thumbnail.Dimmed.Value = true; + + panelBackground.FadeColour(OsuColour.Gray(0.7f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); return base.OnHover(e); } @@ -516,6 +539,8 @@ namespace osu.Game.Screens.OnlinePlay { if (thumbnail != null) thumbnail.Dimmed.Value = false; + + panelBackground.FadeColour(OsuColour.Gray(1f), BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); base.OnHoverLost(e); } @@ -642,7 +667,6 @@ namespace osu.Game.Screens.OnlinePlay backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, }, new FillFlowContainer { @@ -651,7 +675,7 @@ namespace osu.Game.Screens.OnlinePlay Direction = FillDirection.Horizontal, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), - Alpha = 0.5f, + Alpha = 0.6f, Children = new[] { // The left half with no gradient applied From 5ee645ac8f9eb630d58951a7843f3b688e2b885c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:50:21 +0900 Subject: [PATCH 265/670] Increase opacity of control points slightly --- .../Timelines/Summary/Parts/ControlPointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs index 977aadd6c3..17c98003b0 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointVisualisation.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts : base(point.Time) { Point = point; - Alpha = 0.3f; + Alpha = 0.5f; Blending = BlendingParameters.Additive; } From c4de2bbb60b7052a2ccad915cc10148d00df51f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:50:40 +0900 Subject: [PATCH 266/670] Ignore "too many ticks" in timeline (triggers in normal cases) --- .../Components/Timeline/TimelineTickDisplay.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index def528d9e5..4796c08809 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -2,12 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -161,20 +159,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - if (Children.Count > 512) - { - // There should always be a sanely small number of ticks rendered. - // If this assertion triggers, either the zoom logic is broken or a beatmap is - // probably doing weird things... - // - // Let's hope the latter never happens. - // If it does, we can choose to either fix it or ignore it as an outlier. - string message = $"Timeline is rendering many ticks ({Children.Count})"; - - Logger.Log(message); - Debug.Fail(message); - } - int usedDrawables = drawableIndex; // save a few drawables beyond the currently used for edge cases. From c2cc85e6f023bb8089c421ce9f9aa6e676953003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 19:59:38 +0900 Subject: [PATCH 267/670] Use purple again for kiai time specifically --- .../Timelines/Summary/Parts/EffectPointVisualisation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index f1e2b52ad8..17fedb933a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Origin = Anchor.CentreLeft, Height = 0.4f, Depth = float.MaxValue, - Colour = effect.GetRepresentingColour(colours), + Colour = colours.Purple1, }); } } From f500abd4f74f4a5a6f5f717a1e865d9956d1427d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Jul 2024 20:02:17 +0900 Subject: [PATCH 268/670] Make "Hold Off" and "No Release" mod incompatible --- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 +- osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 4e6cc4f1d6..eba0b2effe 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.Conversion; - public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert) }; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModInvert), typeof(ManiaModNoRelease) }; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs index 8cb2e821e6..b5490aa950 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoRelease.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using System.Threading; using osu.Framework.Localisation; @@ -27,6 +28,8 @@ namespace osu.Game.Rulesets.Mania.Mods public override ModType Type => ModType.DifficultyReduction; + public override Type[] IncompatibleMods => new[] { typeof(ManiaModHoldOff) }; + public void ApplyToBeatmap(IBeatmap beatmap) { var maniaBeatmap = (ManiaBeatmap)beatmap; From d7651ef38728b31a2ae011ea4b428483cdc24cc7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 16:52:10 +0300 Subject: [PATCH 269/670] Add extensive test cases for correct input handling while paused in osu! & non-osu! --- .../Gameplay/TestScenePauseInputHandling.cs | 267 ++++++++++++++++++ osu.Game/Screens/Play/HUD/KeyCounter.cs | 8 +- osu.Game/Screens/Play/Player.cs | 4 +- 3 files changed, 274 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs new file mode 100644 index 0000000000..d778f2e991 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -0,0 +1,267 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestScenePauseInputHandling : PlayerTestScene + { + private Ruleset currentRuleset = new OsuRuleset(); + + protected override Ruleset CreatePlayerRuleset() => currentRuleset; + + protected override bool HasCustomSteps => true; + + [Resolved] + private AudioManager audioManager { get; set; } = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => + new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + + [SetUp] + public void SetUp() => Schedule(() => + { + foreach (var key in InputManager.CurrentState.Keyboard.Keys) + InputManager.ReleaseKey(key); + + InputManager.MoveMouseTo(Content); + LocalConfig.SetValue(OsuSetting.KeyOverlay, true); + }); + + [Test] + public void TestOsuInputNotReceivedWhilePaused() + { + KeyCounter counter = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + checkKey(() => counter, 0, false); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 1, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("pause", () => Player.Pause()); + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + + // Z key was released before pause, resuming should not trigger it + checkKey(() => counter, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 1, false); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 2, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 2, false); + } + + [Test] + public void TestManiaInputNotReceivedWhilePaused() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + checkKey(() => counter, 0, false); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("pause", () => Player.Pause()); + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, false); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 2, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 2, false); + } + + [Test] + public void TestOsuPreviouslyHeldInputReleaseOnResume() + { + KeyCounter counterZ = null!; + KeyCounter counterX = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter Z", () => counterZ = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + AddStep("get key counter X", () => counterX = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.RightButton)); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + AddStep("pause", () => Player.Pause()); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press and release Z", () => InputManager.Key(Key.Z)); + checkKey(() => counterZ, 1, false); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + AddStep("pause", () => Player.Pause()); + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + checkKey(() => counterX, 1, true); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, false); + } + + [Test] + public void TestManiaPreviouslyHeldInputReleaseOnResume() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("pause", () => Player.Pause()); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, false); + } + + [Test] + public void TestOsuHeldInputRemainHeldAfterResume() + { + KeyCounter counterZ = null!; + KeyCounter counterX = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter Z", () => counterZ = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + AddStep("get key counter X", () => counterX = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.RightButton)); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + AddStep("pause", () => Player.Pause()); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 1, false); + + AddStep("press X", () => InputManager.PressKey(Key.X)); + checkKey(() => counterX, 1, true); + + AddStep("pause", () => Player.Pause()); + + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + AddStep("press X", () => InputManager.PressKey(Key.X)); + + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, true); + + AddStep("release X", () => InputManager.ReleaseKey(Key.X)); + checkKey(() => counterZ, 1, false); + checkKey(() => counterX, 1, false); + } + + [Test] + public void TestManiaHeldInputRemainHeldAfterResume() + { + KeyCounter counter = null!; + + loadPlayer(() => new ManiaRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + + AddStep("press D", () => InputManager.PressKey(Key.D)); + checkKey(() => counter, 1, true); + + AddStep("pause", () => Player.Pause()); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("press D", () => InputManager.PressKey(Key.D)); + + AddStep("resume", () => Player.Resume()); + AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); + checkKey(() => counter, 1, true); + + AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + checkKey(() => counter, 1, false); + } + + private void loadPlayer(Func createRuleset) + { + AddStep("set ruleset", () => currentRuleset = createRuleset()); + AddStep("load player", LoadPlayer); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); + + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500)); + AddAssert("not in break", () => !Player.IsBreakTime.Value); + } + + private void checkKey(Func counter, int count, bool active) + { + AddAssert($"key count = {count}", () => counter().CountPresses.Value, () => Is.EqualTo(count)); + AddAssert($"key active = {active}", () => counter().IsActive.Value, () => Is.EqualTo(active)); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + private partial class PausePlayer : TestPlayer + { + protected override double PauseCooldownDuration => 0; + + public PausePlayer() + : base(allowPause: true, showResults: false) + { + } + } + } +} diff --git a/osu.Game/Screens/Play/HUD/KeyCounter.cs b/osu.Game/Screens/Play/HUD/KeyCounter.cs index f12d2166fc..66f9dfd6f2 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounter.cs @@ -24,7 +24,9 @@ namespace osu.Game.Screens.Play.HUD /// /// Whether this is currently in the "activated" state because the associated key is currently pressed. /// - protected readonly Bindable IsActive = new BindableBool(); + public IBindable IsActive => isActive; + + private readonly Bindable isActive = new BindableBool(); protected KeyCounter(InputTrigger trigger) { @@ -36,12 +38,12 @@ namespace osu.Game.Screens.Play.HUD protected virtual void Activate(bool forwardPlayback = true) { - IsActive.Value = true; + isActive.Value = true; } protected virtual void Deactivate(bool forwardPlayback = true) { - IsActive.Value = false; + isActive.Value = false; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3a08d3be24..4a419e1431 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -987,14 +987,14 @@ namespace osu.Game.Screens.Play /// /// The amount of gameplay time after which a second pause is allowed. /// - private const double pause_cooldown = 1000; + protected virtual double PauseCooldownDuration => 1000; protected PauseOverlay PauseOverlay { get; private set; } private double? lastPauseActionTime; protected bool PauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + pause_cooldown; + lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + PauseCooldownDuration; /// /// A set of conditionals which defines whether the current game state and configuration allows for From 4f6c7fe7c3c6941db20abc28cf321c03fa58646c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 16:52:48 +0300 Subject: [PATCH 270/670] Schedule resume operation by one frame to ensure the triggered key down event does not cause a gameplay press --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index a04ea80640..8a137e6665 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.UI scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); - ResumeRequested?.Invoke(); + Schedule(() => ResumeRequested?.Invoke()); return true; } From 818b60a3d80aa5e3c702fadf9cd83397e56e2a66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 18:18:37 +0300 Subject: [PATCH 271/670] Fix pause overlay hiding input from ruleset input manager If a key is pressed while the pause overlay is visible, the ruleset input manager will not see it, therefore if the user resumes while the key is held then releases the key, the ruleset input manager will not receive the key up event. --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index da239d585e..2b961278d5 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Play private const int button_height = 70; private const float background_alpha = 0.75f; - protected override bool BlockNonPositionalInput => true; - protected override bool BlockScrollInput => false; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; From e539670df1d6d73c077b5a3792ea27c3318e464e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 19 Jul 2024 19:19:36 +0300 Subject: [PATCH 272/670] Add explanatory note --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8a137e6665..d809f2b318 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -116,6 +116,8 @@ namespace osu.Game.Rulesets.Osu.UI scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); + // When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score. + // To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events. Schedule(() => ResumeRequested?.Invoke()); return true; } From d914b990f3ee62b5aacb48c5893340aff396c73c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Jul 2024 14:08:00 +0900 Subject: [PATCH 273/670] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index fe0a452e92..7785cb3c94 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index acfcae7c93..dceb88c6f7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From e2fe1935a92943c5e505bf82cf76e4e59ab93298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:22:36 +0200 Subject: [PATCH 274/670] Add failing test case --- .../Editor/TestSceneEditorTestGameplay.cs | 70 +++++++++++++++++++ .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 + 2 files changed, 71 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs new file mode 100644 index 0000000000..2422e62571 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Storyboards; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Tests.Editor +{ + public partial class TestSceneTaikoEditorTestGameplay : EditorTestScene + { + protected override bool IsolateSavingFromDatabase => false; + + protected override Ruleset CreateEditorRuleset() => new TaikoRuleset(); + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + private BeatmapSetInfo importedBeatmapSet = null!; + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely()); + base.SetUpSteps(); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)); + + [Test] + public void TestBasicGameplayTest() + { + AddStep("add objects", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new Swell { StartTime = 500, EndTime = 1500 }); + EditorBeatmap.Add(new Hit { StartTime = 3000 }); + }); + AddStep("seek to 250", () => EditorClock.Seek(250)); + AddUntilStep("wait for seek", () => EditorClock.CurrentTime, () => Is.EqualTo(250)); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveRequiredPopupDialog); + + AddStep("save changes", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("player pushed", () => Stack.CurrentScreen is EditorPlayer); + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Screens.Edit.Editor); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 26afd42445..a2420fc679 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -11,5 +11,6 @@ + From 157cc884f4c48c3e130f655fe60343a3dfda1cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:23:28 +0200 Subject: [PATCH 275/670] Fix swells not being correctly treated in editor gameplay test Closes https://github.com/ppy/osu/issues/28989. Because swell ticks are judged manually by their parenting objects, swell ticks were not given a start time (with the thinking that there isn't really one *to* give). This tripped up the "judge past objects" logic in `EditorPlayer`, since it would enumerate all objects (regardless of nesting) that are prior to current time and mark them as judged. With all swell ticks having the default start time of 0 they would get judged more often than not, leading to behaviour weirdness. To resolve, give swell ticks a *relatively* sane start time equal to the start time of the swell itself. --- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index a8db8df021..d9e8c77ea7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Objects cancellationToken.ThrowIfCancellationRequested(); AddNested(new SwellTick { + StartTime = StartTime, Samples = Samples }); } From 636e965868866283b3848e74281ab6df897a9e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:26:04 +0200 Subject: [PATCH 276/670] Remove no-longer-valid test remark & adjust test --- .../TestSceneDrumSampleTriggerSource.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs index 6c925f566b..b47f02afa3 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumSampleTriggerSource.cs @@ -315,10 +315,7 @@ namespace osu.Game.Rulesets.Taiko.Tests hitObjectContainer.Add(drawableSwell); }); - // You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero). - // This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits. - // But for sample playback purposes they can be ignored as noise. - AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf); + AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK); checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, SampleControlPoint.DEFAULT_BANK); @@ -352,10 +349,7 @@ namespace osu.Game.Rulesets.Taiko.Tests hitObjectContainer.Add(drawableSwell); }); - // You might think that this should be a SwellTick since we're before the swell, but SwellTicks get no StartTime (ie. they are zero). - // This works fine in gameplay because they are judged whenever the user pressed, rather than being timed hits. - // But for sample playback purposes they can be ignored as noise. - AddAssert("most valid object is swell", () => triggerSource.GetMostValidObject(), Is.InstanceOf); + AddAssert("most valid object is swell tick", () => triggerSource.GetMostValidObject(), Is.InstanceOf); checkSamples(HitType.Centre, false, HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_DRUM); checkSamples(HitType.Rim, false, HitSampleInfo.HIT_CLAP, HitSampleInfo.BANK_DRUM); From 57fa502786c2c7a609b8b9428a3b4fd082fd546d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 11:57:46 +0200 Subject: [PATCH 277/670] Fix editor UI dimming when hovering over expanded part of toolboxes Closes https://github.com/ppy/osu/issues/28969. --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3c38a7258e..c2a7bec9f9 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -305,7 +305,9 @@ namespace osu.Game.Rulesets.Edit PlayfieldContentContainer.X = TOOLBOX_CONTRACTED_SIZE_LEFT; } - composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position); + composerFocusMode.Value = PlayfieldContentContainer.Contains(InputManager.CurrentState.Mouse.Position) + && !LeftToolbox.Contains(InputManager.CurrentState.Mouse.Position) + && !RightToolbox.Contains(InputManager.CurrentState.Mouse.Position); } public override Playfield Playfield => drawableRulesetWrapper.Playfield; From 64381d4087994086ee9b0bdada5e06e23a0f3f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 12:18:53 +0200 Subject: [PATCH 278/670] Fix catch juice stream vertex add operation not undoing --- .../Edit/Blueprints/Components/SelectionEditablePath.cs | 9 ++++++++- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index c7a26ca15a..c4e906d5dc 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -19,22 +20,28 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public MenuItem[] ContextMenuItems => getContextMenuItems().ToArray(); + private readonly JuiceStream juiceStream; + // To handle when the editor is scrolled while dragging. private Vector2 dragStartPosition; [Resolved] private IEditorChangeHandler? changeHandler { get; set; } - public SelectionEditablePath(Func positionToTime) + public SelectionEditablePath(JuiceStream juiceStream, Func positionToTime) : base(positionToTime) { + this.juiceStream = juiceStream; } public void AddVertex(Vector2 relativePosition) { + changeHandler?.BeginChange(); double time = Math.Max(0, PositionToTime(relativePosition.Y)); int index = AddVertex(time, relativePosition.X); + UpdateHitObjectFromPath(juiceStream); selectOnly(index); + changeHandler?.EndChange(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 49d778ad08..a492920d3a 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { scrollingPath = new ScrollingPath(), nestedOutlineContainer = new NestedOutlineContainer(), - editablePath = new SelectionEditablePath(positionToTime) + editablePath = new SelectionEditablePath(hitObject, positionToTime) }; } From 47964f33d77aca5dd4305313aac0f7216648a000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:21:49 +0200 Subject: [PATCH 279/670] Fix catch juice stream vertex remove operation not undoing --- .../Blueprints/Components/SelectionEditablePath.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index c4e906d5dc..904d7a2579 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -54,7 +54,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components if (e.Button == MouseButton.Left && e.ShiftPressed) { + changeHandler?.BeginChange(); RemoveVertex(index); + UpdateHitObjectFromPath(juiceStream); + changeHandler?.EndChange(); + return true; } @@ -125,11 +129,17 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components private void deleteSelectedVertices() { + changeHandler?.BeginChange(); + for (int i = VertexCount - 1; i >= 0; i--) { if (VertexStates[i].IsSelected) RemoveVertex(i); } + + UpdateHitObjectFromPath(juiceStream); + + changeHandler?.EndChange(); } } } From 6b3c1f4e47abc22a60bddaa6dbc0c8a43d13bff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:26:16 +0200 Subject: [PATCH 280/670] Unify juice stream piece UX with osu! control point pieces - Use same hover state - Use shift-right click for quick delete rather than shift-left click --- .../Components/SelectionEditablePath.cs | 2 +- .../Edit/Blueprints/Components/VertexPiece.cs | 30 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index 904d7a2579..6a4e35b1f9 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components if (index == -1 || VertexStates[index].IsFixed) return false; - if (e.Button == MouseButton.Left && e.ShiftPressed) + if (e.Button == MouseButton.Right && e.ShiftPressed) { changeHandler?.BeginChange(); RemoveVertex(index); diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs index 07d7c72698..a3f8e85278 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/VertexPiece.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; @@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { public partial class VertexPiece : Circle { + private VertexState state = new VertexState(); + [Resolved] private OsuColour osuColour { get; set; } = null!; @@ -24,7 +27,32 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateFrom(VertexState state) { - Colour = state.IsSelected ? osuColour.Yellow.Lighten(1) : osuColour.Yellow; + this.state = state; + updateMarkerDisplay(); + } + + protected override bool OnHover(HoverEvent e) + { + updateMarkerDisplay(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateMarkerDisplay(); + } + + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + var colour = osuColour.Yellow; + + if (IsHovered || state.IsSelected) + colour = colour.Lighten(1); + + Colour = colour; Alpha = state.IsFixed ? 0.5f : 1; } } From 1d91201c4303e1cb32470b86cb2ff1d8f306b0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:33:51 +0200 Subject: [PATCH 281/670] Fix tests --- .../Editor/TestSceneJuiceStreamSelectionBlueprint.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index c96f32d87c..10cf294a36 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -158,14 +158,14 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor float[] positions = { 200, 200 }; addBlueprintStep(times, positions, 0.2); - addAddVertexSteps(500, 150); - addVertexCheckStep(3, 1, 500, 150); + addAddVertexSteps(500, 180); + addVertexCheckStep(3, 1, 500, 180); addAddVertexSteps(90, 200); addVertexCheckStep(4, 1, times[0], positions[0]); - addAddVertexSteps(750, 180); - addVertexCheckStep(5, 4, 750, 180); + addAddVertexSteps(750, 200); + addVertexCheckStep(5, 4, 750, 200); AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3)); } @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("delete vertex", () => { InputManager.PressKey(Key.ShiftLeft); - InputManager.Click(MouseButton.Left); + InputManager.Click(MouseButton.Right); InputManager.ReleaseKey(Key.ShiftLeft); }); } From f86ab1a64e8137bf89d6c082eefe4d3d72ed7466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 13:49:52 +0200 Subject: [PATCH 282/670] Fix filename --- ...eEditorTestGameplay.cs => TestSceneTaikoEditorTestGameplay.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Rulesets.Taiko.Tests/Editor/{TestSceneEditorTestGameplay.cs => TestSceneTaikoEditorTestGameplay.cs} (100%) diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorTestGameplay.cs similarity index 100% rename from osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneEditorTestGameplay.cs rename to osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoEditorTestGameplay.cs From 56af009e7759d364b6ded4d57d0f9c32c1f6dbd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:47:33 +0200 Subject: [PATCH 283/670] Fix `EditablePath.UpdateHitObjectFromPath()` not automatically updating object This is important because the editable path conversions heavily depend on the value of `JuiceStream.Velocity` being correct. The value is only guaranteed to be correct after an `ApplyDefaults()` call, which is triggered by updating the object via `EditorBeatmap`. --- .../Blueprints/Components/EditablePath.cs | 5 ++++ .../Components/SelectionEditablePath.cs | 23 ++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 86f92d16ca..857c00cd41 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; using osuTK; namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components @@ -42,6 +43,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components [Resolved] private IBeatSnapProvider? beatSnapProvider { get; set; } + [Resolved] + protected EditorBeatmap? EditorBeatmap { get; private set; } + protected EditablePath(Func positionToTime) { PositionToTime = positionToTime; @@ -112,6 +116,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; + EditorBeatmap?.Update(hitObject); } public Vector2 ToRelativePosition(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index 6a4e35b1f9..b2ee43ba16 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -25,9 +23,6 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // To handle when the editor is scrolled while dragging. private Vector2 dragStartPosition; - [Resolved] - private IEditorChangeHandler? changeHandler { get; set; } - public SelectionEditablePath(JuiceStream juiceStream, Func positionToTime) : base(positionToTime) { @@ -36,12 +31,14 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void AddVertex(Vector2 relativePosition) { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); + double time = Math.Max(0, PositionToTime(relativePosition.Y)); int index = AddVertex(time, relativePosition.X); UpdateHitObjectFromPath(juiceStream); selectOnly(index); - changeHandler?.EndChange(); + + EditorBeatmap?.EndChange(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => InternalChildren.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); @@ -54,10 +51,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components if (e.Button == MouseButton.Right && e.ShiftPressed) { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); RemoveVertex(index); UpdateHitObjectFromPath(juiceStream); - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); return true; } @@ -85,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components for (int i = 0; i < VertexCount; i++) VertexStates[i].VertexBeforeChange = Vertices[i]; - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); return true; } @@ -99,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components protected override void OnDragEnd(DragEndEvent e) { - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } private int getMouseTargetVertex(Vector2 screenSpacePosition) @@ -129,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components private void deleteSelectedVertices() { - changeHandler?.BeginChange(); + EditorBeatmap?.BeginChange(); for (int i = VertexCount - 1; i >= 0; i--) { @@ -139,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components UpdateHitObjectFromPath(juiceStream); - changeHandler?.EndChange(); + EditorBeatmap?.EndChange(); } } } From f3617eadad1f997d5cd4eea45eb28c22467c25f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:51:45 +0200 Subject: [PATCH 284/670] Fix editing juice stream path sometimes changing its duration I'm not *super* sure why this works, but it appears to, and my educated guess as to why is that it counteracts the effects of a change in the SV of the juice stream by artificially increasing or decreasing the velocity when running the appropriate path conversions and expected distance calculations. The actual SV change takes effect on the next default application, which is triggered by the `Update()` call at the end of the method. --- .../Edit/Blueprints/Components/EditablePath.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 857c00cd41..e626392234 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -107,15 +107,22 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // // The value is clamped here by the bindable min and max values. // In case the required velocity is too large, the path is not preserved. + double previousVelocity = svBindable.Value; svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); - path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); + // adjust velocity locally, so that once the SV change is applied by applying defaults + // (triggered by `EditorBeatmap.Update()` call at end of method), + // it results in the outcome desired by the user. + double relativeChange = svBindable.Value / previousVelocity; + double localVelocity = hitObject.Velocity * relativeChange; + path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, localVelocity); if (beatSnapProvider == null) return; double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); - hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; + hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * localVelocity; + EditorBeatmap?.Update(hitObject); } From 6100f5269d757177d557af714a17a8c116530dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Jul 2024 14:57:36 +0200 Subject: [PATCH 285/670] Fix tests --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 10cf294a36..7b665b1ff9 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -82,6 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddMouseMoveStep(-100, 100); addVertexCheckStep(3, 1, times[0], positions[0]); + addDragEndStep(); } [Test] @@ -100,6 +101,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddMouseMoveStep(times[2] - 50, positions[2] - 50); addVertexCheckStep(4, 1, times[1] - 50, positions[1] - 50); addVertexCheckStep(4, 2, times[2] - 50, positions[2] - 50); + + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); + addDragEndStep(); } [Test] @@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor addDragStartStep(times[1], positions[1]); AddMouseMoveStep(times[1], 400); AddAssert("slider velocity changed", () => !hitObject.SliderVelocityMultiplierBindable.IsDefault); + addDragEndStep(); } [Test] @@ -129,6 +134,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddStep("scroll playfield", () => manualClock.CurrentTime += 200); AddMouseMoveStep(times[1] + 200, positions[1] + 100); addVertexCheckStep(2, 1, times[1] + 200, positions[1] + 100); + addDragEndStep(); } [Test] @@ -158,21 +164,21 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor float[] positions = { 200, 200 }; addBlueprintStep(times, positions, 0.2); - addAddVertexSteps(500, 180); - addVertexCheckStep(3, 1, 500, 180); + addAddVertexSteps(500, 150); + addVertexCheckStep(3, 1, 500, 150); - addAddVertexSteps(90, 200); - addVertexCheckStep(4, 1, times[0], positions[0]); + addAddVertexSteps(160, 200); + addVertexCheckStep(4, 1, 160, 200); - addAddVertexSteps(750, 200); - addVertexCheckStep(5, 4, 750, 200); + addAddVertexSteps(750, 180); + addVertexCheckStep(5, 4, 800, 160); AddAssert("duration is changed", () => Precision.AlmostEquals(hitObject.Duration, 800 - times[0], 1e-3)); } [Test] public void TestDeleteVertex() { - double[] times = { 100, 300, 500 }; + double[] times = { 100, 300, 400 }; float[] positions = { 100, 200, 150 }; addBlueprintStep(times, positions); From 38fc6f70f63a1dbff8b1b1848ea61c9c6b9bd0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 11:05:53 +0200 Subject: [PATCH 286/670] Add tolerance when drag-scrolling editor timeline Closes https://github.com/ppy/osu/issues/28983. While the direct cause of this is most likely mouse confine in full-screen, it shouldn't/can't really be disabled just for this, and I also get this on linux in *windowed* mode. In checking other apps, adding some tolerance to this sort of drag-scroll behaviour seems like a sane UX improvement anyways. --- .../Timeline/TimelineBlueprintContainer.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 9a8fdc3dac..62c15996e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -198,11 +198,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline var timelineQuad = timeline.ScreenSpaceDrawQuad; float mouseX = InputManager.CurrentState.Mouse.Position.X; + // for better UX do not require the user to drag all the way to the edge and beyond to initiate a drag-scroll. + // this is especially important in scenarios like fullscreen, where mouse confine will usually be on + // and the user physically *won't be able to* drag beyond the edge of the timeline + // (since its left edge is co-incident with the window edge). + const float scroll_tolerance = 20; + + float leftBound = timelineQuad.TopLeft.X + scroll_tolerance; + float rightBound = timelineQuad.TopRight.X - scroll_tolerance; + // scroll if in a drag and dragging outside visible extents - if (mouseX > timelineQuad.TopRight.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < timelineQuad.TopLeft.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + if (mouseX > rightBound) + timeline.ScrollBy((float)((mouseX - rightBound) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < leftBound) + timeline.ScrollBy((float)((mouseX - leftBound) / 10 * Clock.ElapsedFrameTime)); } private partial class SelectableAreaBackground : CompositeDrawable From cc4ed0ff3f9892052128adfe5b46ded90e918f95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2024 18:59:22 +0900 Subject: [PATCH 287/670] Use non-screen-space coordinates and add time-based drag ramping for better control --- .../Timeline/TimelineBlueprintContainer.cs | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 62c15996e0..ca23e3e88f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -100,10 +101,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnDragStart(e); } + private float dragTimeAccumulated; + protected override void Update() { if (IsDragged || hitObjectDragged) handleScrollViaDrag(); + else + dragTimeAccumulated = 0; if (Composer != null && timeline != null) { @@ -193,25 +198,42 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void handleScrollViaDrag() { + // The amount of time dragging before we reach maximum drag speed. + const float time_ramp_multiplier = 5000; + + // A maximum drag speed to ensure things don't get out of hand. + const float max_velocity = 10; + if (timeline == null) return; - var timelineQuad = timeline.ScreenSpaceDrawQuad; - float mouseX = InputManager.CurrentState.Mouse.Position.X; + var mousePos = timeline.ToLocalSpace(InputManager.CurrentState.Mouse.Position); // for better UX do not require the user to drag all the way to the edge and beyond to initiate a drag-scroll. // this is especially important in scenarios like fullscreen, where mouse confine will usually be on // and the user physically *won't be able to* drag beyond the edge of the timeline // (since its left edge is co-incident with the window edge). - const float scroll_tolerance = 20; + const float scroll_tolerance = 40; - float leftBound = timelineQuad.TopLeft.X + scroll_tolerance; - float rightBound = timelineQuad.TopRight.X - scroll_tolerance; + float leftBound = timeline.BoundingBox.TopLeft.X + scroll_tolerance; + float rightBound = timeline.BoundingBox.TopRight.X - scroll_tolerance; - // scroll if in a drag and dragging outside visible extents - if (mouseX > rightBound) - timeline.ScrollBy((float)((mouseX - rightBound) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < leftBound) - timeline.ScrollBy((float)((mouseX - leftBound) / 10 * Clock.ElapsedFrameTime)); + float amount = 0; + + if (mousePos.X > rightBound) + amount = mousePos.X - rightBound; + else if (mousePos.X < leftBound) + amount = mousePos.X - leftBound; + + if (amount == 0) + { + dragTimeAccumulated = 0; + return; + } + + amount = Math.Sign(amount) * Math.Min(max_velocity, Math.Abs(MathF.Pow(amount, 2) / (MathF.Pow(scroll_tolerance, 2)))); + dragTimeAccumulated += (float)Clock.ElapsedFrameTime; + + timeline.ScrollBy(amount * (float)Clock.ElapsedFrameTime * Math.Min(1, dragTimeAccumulated / time_ramp_multiplier)); } private partial class SelectableAreaBackground : CompositeDrawable From ad1a86ebdcfc8f05380331c2aae3c0f46b084496 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:05:14 +0800 Subject: [PATCH 288/670] Implement the overlay --- osu.Game/Skinning/LegacyKeyCounter.cs | 101 +++++++++++++++++++ osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 79 +++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 20 ++-- 3 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Skinning/LegacyKeyCounter.cs create mode 100644 osu.Game/Skinning/LegacyKeyCounterDisplay.cs diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs new file mode 100644 index 0000000000..73534a8e51 --- /dev/null +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -0,0 +1,101 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Skinning +{ + public partial class LegacyKeyCounter : KeyCounter + { + public bool UsesFixedAnchor { get; set; } + + public float TransitionDuration { get; set; } = 150f; + + public Colour4 KeyTextColour { get; set; } = Colour4.White; + + public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + + private Container keyContainer = null!; + + private SkinnableSprite overlayKey = null!; + + private OsuSpriteText overlayKeyText = null!; + + public LegacyKeyCounter(InputTrigger trigger) + : base(trigger) + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + Child = keyContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + overlayKey = new SkinnableSprite + { + Blending = Multiplicative, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BypassAutoSizeAxes = Axes.Both, + SpriteName = { Value = "inputoverlay-key" }, + Rotation = -90, + }, + overlayKeyText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = trigger.Name, + Colour = KeyTextColour, + Font = OsuFont.Default.With(fixedWidth: true), + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Width = Math.Max(overlayKey.Width, 43 * 1.05f); + Height = Math.Max(overlayKey.Height, 43 * 1.05f); + } + + protected override void Activate(bool forwardPlayback = true) + { + base.Activate(forwardPlayback); + keyContainer.ScaleTo(0.75f, TransitionDuration); + keyContainer.FadeColour(KeyDownBackgroundColour, TransitionDuration); + overlayKeyText.Text = CountPresses.Value.ToString(); + } + + protected override void Deactivate(bool forwardPlayback = true) + { + base.Deactivate(forwardPlayback); + keyContainer.ScaleTo(1f, TransitionDuration); + keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); + } + + public static BlendingParameters Multiplicative + { + get + { + BlendingParameters result = default(BlendingParameters); + result.Source = BlendingType.SrcAlpha; + result.Destination = BlendingType.OneMinusSrcAlpha; + result.SourceAlpha = BlendingType.One; + result.DestinationAlpha = BlendingType.One; + result.RGBEquation = BlendingEquation.Add; + result.AlphaEquation = BlendingEquation.Add; + return result; + } + } + } +} diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs new file mode 100644 index 0000000000..ace582af5c --- /dev/null +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -0,0 +1,79 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osuTK.Graphics; +using osu.Framework.Allocation; + +namespace osu.Game.Skinning +{ + public partial class LegacyKeyCounterDisplay : KeyCounterDisplay + { + private const float key_transition_time = 50; + + protected override FillFlowContainer KeyFlow { get; } = null!; + + private SkinnableSprite overlayBackground = null!; + + public LegacyKeyCounterDisplay() + { + AutoSizeAxes = Axes.Both; + + AddRangeInternal(new Drawable[] + { + overlayBackground = new SkinnableSprite + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + //BypassAutoSizeAxes = Axes.Both, + SpriteName = { Value= "inputoverlay-background" }, + }, + KeyFlow = new FillFlowContainer + { + Padding = new MarginPadding + { + Horizontal = 7f * 1.05f, + }, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }, + }); + } + + [BackgroundDependencyLoader] + private void load(ISkinSource source) + { + source.GetConfig("InputOverlayText")?.BindValueChanged(v => + { + KeyTextColor = v.NewValue; + }, true); + } + + protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) + { + TransitionDuration = key_transition_time, + KeyTextColour = keyTextColor, + }; + + private Color4 keyTextColor = Color4.White; + + public Color4 KeyTextColor + { + get => keyTextColor; + set + { + if (value != keyTextColor) + { + keyTextColor = value; + foreach (var child in KeyFlow.Cast()) + child.KeyTextColour = value; + } + } + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 816cfc0a2d..a890c5ffab 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -381,22 +382,22 @@ namespace osu.Game.Skinning } var hitError = container.OfType().FirstOrDefault(); - var keyCounter = container.OfType().FirstOrDefault(); if (hitError != null) { hitError.Anchor = Anchor.BottomCentre; hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; + } - if (keyCounter != null) - { - const float padding = 10; + var keyCounter = container.OfType().FirstOrDefault(); - keyCounter.Anchor = Anchor.BottomRight; - keyCounter.Origin = Anchor.BottomRight; - keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width)); - } + if (keyCounter != null) + { + keyCounter.Rotation = 90f; + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.TopCentre; + keyCounter.Position = new Vector2(0); } }) { @@ -408,7 +409,8 @@ namespace osu.Game.Skinning new LegacySongProgress(), new LegacyHealthDisplay(), new BarHitErrorMeter(), - new DefaultKeyCounterDisplay() + //new DefaultKeyCounterDisplay(), + new LegacyKeyCounterDisplay(), } }; } From 25d63ac6a59ae4c4ada282eaf7c5d0b20376331e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:25:38 +0200 Subject: [PATCH 289/670] Move editor beatmap processor test cases off of `OsuHitObject`s Most of them are about to become obsolete once consideration for `TimePreempt` is re-added. --- .../TestSceneEditorBeatmapProcessor.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs index 251099c0e2..c4a61177a9 100644 --- a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -8,7 +8,6 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -45,7 +44,7 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, + new Note { StartTime = 1000 }, } }); @@ -67,8 +66,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, } }); @@ -136,8 +135,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, } }); @@ -164,8 +163,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -197,9 +196,9 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -232,8 +231,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1100 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1100 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -264,8 +263,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -299,9 +298,9 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 5000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 5000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -334,8 +333,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 9000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 9000 }, }, Breaks = { @@ -366,8 +365,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, }, Breaks = { @@ -393,8 +392,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 1000 }, - new HitCircle { StartTime = 2000 }, + new Note { StartTime = 1000 }, + new Note { StartTime = 2000 }, }, Breaks = { @@ -447,8 +446,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 10000 }, - new HitCircle { StartTime = 11000 }, + new Note { StartTime = 10000 }, + new Note { StartTime = 11000 }, }, Breaks = { @@ -474,8 +473,8 @@ namespace osu.Game.Tests.Editing BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, HitObjects = { - new HitCircle { StartTime = 10000 }, - new HitCircle { StartTime = 11000 }, + new Note { StartTime = 10000 }, + new Note { StartTime = 11000 }, }, Breaks = { From 088e8ad0a27415fd0b93e79e0126a18051191d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:30:10 +0200 Subject: [PATCH 290/670] Respect pre-empt time when auto-generating breaks Closes https://github.com/ppy/osu/issues/28703. --- .../Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 4 ++-- .../Rulesets/Objects/Types/IHasTimePreempt.cs | 13 +++++++++++++ .../Screens/Edit/EditorBeatmapProcessor.cs | 18 +++++++++++++----- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 52c42dfddb..329055b3dd 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Objects { - public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation + public abstract class CatchHitObject : HitObject, IHasPosition, IHasComboInformation, IHasTimePreempt { public const float OBJECT_RADIUS = 64; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6c77d9189c..1b0993b698 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition + public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition, IHasTimePreempt { /// /// The radius of hit objects (ie. the radius of a ). @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public const double PREEMPT_MAX = 1800; - public double TimePreempt = 600; + public double TimePreempt { get; set; } = 600; public double TimeFadeIn = 400; private HitObjectProperty position; diff --git a/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs b/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs new file mode 100644 index 0000000000..e7239515f6 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasTimePreempt.cs @@ -0,0 +1,13 @@ +// 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.Objects.Types +{ + /// + /// A that appears on screen at a fixed time interval before its . + /// + public interface IHasTimePreempt + { + double TimePreempt { get; } + } +} diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 9b6d956a4c..5c435e771d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit { @@ -67,19 +68,26 @@ namespace osu.Game.Screens.Edit for (int i = 1; i < Beatmap.HitObjects.Count; ++i) { + var previousObject = Beatmap.HitObjects[i - 1]; + var nextObject = Beatmap.HitObjects[i]; + // Keep track of the maximum end time encountered thus far. // This handles cases like osu!mania's hold notes, which could have concurrent other objects after their start time. // Note that we're relying on the implicit assumption that objects are sorted by start time, // which is why similar tracking is not done for start time. - currentMaxEndTime = Math.Max(currentMaxEndTime, Beatmap.HitObjects[i - 1].GetEndTime()); + currentMaxEndTime = Math.Max(currentMaxEndTime, previousObject.GetEndTime()); - double nextObjectStartTime = Beatmap.HitObjects[i].StartTime; - - if (nextObjectStartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION) + if (nextObject.StartTime - currentMaxEndTime < BreakPeriod.MIN_GAP_DURATION) continue; double breakStartTime = currentMaxEndTime + BreakPeriod.GAP_BEFORE_BREAK; - double breakEndTime = nextObjectStartTime - Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObjectStartTime).BeatLength * 2); + + double breakEndTime = nextObject.StartTime; + + if (nextObject is IHasTimePreempt hasTimePreempt) + breakEndTime -= hasTimePreempt.TimePreempt; + else + breakEndTime -= Math.Max(BreakPeriod.GAP_AFTER_BREAK, Beatmap.ControlPointInfo.TimingPointAt(nextObject.StartTime).BeatLength * 2); if (breakEndTime - breakStartTime < BreakPeriod.MIN_BREAK_DURATION) continue; From c2fa30bf81b46316c6043944c73d4519db4a73cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:38:25 +0200 Subject: [PATCH 291/670] Add test coverage for break generation respecting pre-empt time --- .../TestSceneEditorBeatmapProcessor.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs index c4a61177a9..bbcf6aac2c 100644 --- a/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs +++ b/osu.Game.Tests/Editing/TestSceneEditorBeatmapProcessor.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Editing @@ -488,5 +489,55 @@ namespace osu.Game.Tests.Editing Assert.That(beatmap.Breaks, Is.Empty); } + + [Test] + public void TestTimePreemptIsRespected() + { + var controlPoints = new ControlPointInfo(); + controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 }); + var beatmap = new EditorBeatmap(new Beatmap + { + ControlPointInfo = controlPoints, + BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo }, + Difficulty = + { + ApproachRate = 10, + }, + HitObjects = + { + new HitCircle { StartTime = 1000 }, + new HitCircle { StartTime = 5000 }, + } + }); + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset()); + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MIN)); + }); + + beatmap.Difficulty.ApproachRate = 0; + + foreach (var ho in beatmap.HitObjects) + ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); + + beatmapProcessor.PreProcess(); + beatmapProcessor.PostProcess(); + + Assert.Multiple(() => + { + Assert.That(beatmap.Breaks, Has.Count.EqualTo(1)); + Assert.That(beatmap.Breaks[0].StartTime, Is.EqualTo(1200)); + Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX)); + }); + } } } From c3062f96eee5d3a7a92a5f105bf4c62d024d3572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 13:38:50 +0200 Subject: [PATCH 292/670] Fix autogenerated breaks not invalidating on change to pre-empt time --- osu.Game/Screens/Edit/EditorBeatmapProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs index 5c435e771d..4fe431498f 100644 --- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs +++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit private void autoGenerateBreaks() { - var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime, ho.GetEndTime())).ToHashSet(); + var objectDuration = Beatmap.HitObjects.Select(ho => (ho.StartTime - ((ho as IHasTimePreempt)?.TimePreempt ?? 0), ho.GetEndTime())).ToHashSet(); if (objectDuration.SetEquals(objectDurationCache)) return; From 777a0deb0fcc814b0b4f59a1f2a67011462d0556 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:45:53 +0800 Subject: [PATCH 293/670] Update the offset formula --- osu.Game/Skinning/LegacyKeyCounter.cs | 4 ++-- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 73534a8e51..c3125c3eda 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -64,8 +64,8 @@ namespace osu.Game.Skinning protected override void LoadComplete() { base.LoadComplete(); - Width = Math.Max(overlayKey.Width, 43 * 1.05f); - Height = Math.Max(overlayKey.Height, 43 * 1.05f); + Width = Math.Max(overlayKey.Width, 48 * 0.95f); + Height = Math.Max(overlayKey.Height, 48 * 0.95f); } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index ace582af5c..e395aefe8a 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -28,15 +28,14 @@ namespace osu.Game.Skinning { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - //BypassAutoSizeAxes = Axes.Both, SpriteName = { Value= "inputoverlay-background" }, }, KeyFlow = new FillFlowContainer { - Padding = new MarginPadding - { - Horizontal = 7f * 1.05f, - }, + // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay + // 24px away from the container, there're 4 counter in legacy, so divide by 4 + // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate + X = (24 / 4) * 1.05f, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Horizontal, From aed7ba9508b636a00a7e38c7e6519fe2bbb87af3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jul 2024 20:56:21 +0900 Subject: [PATCH 294/670] Change order of application to avoid bias to side with more room to drag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- .../Compose/Components/Timeline/TimelineBlueprintContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index ca23e3e88f..740f0b6aac 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return; } - amount = Math.Sign(amount) * Math.Min(max_velocity, Math.Abs(MathF.Pow(amount, 2) / (MathF.Pow(scroll_tolerance, 2)))); + amount = Math.Sign(amount) * Math.Min(max_velocity, MathF.Pow(Math.Clamp(Math.Abs(amount), 0, scroll_tolerance), 2)); dragTimeAccumulated += (float)Clock.ElapsedFrameTime; timeline.ScrollBy(amount * (float)Clock.ElapsedFrameTime * Math.Min(1, dragTimeAccumulated / time_ramp_multiplier)); From 5dcc8b7a8f403dcfa6e2f1e6cfba409224cf844c Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 19:56:43 +0800 Subject: [PATCH 295/670] Make the text are always horizontal --- osu.Game/Skinning/LegacyKeyCounter.cs | 14 +++++++++++++- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index c3125c3eda..bb1532d75d 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -20,6 +20,17 @@ namespace osu.Game.Skinning public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + private float keyTextRotation = 0f; + public float KeyTextRotation + { + get => keyTextRotation; + set + { + keyTextRotation = value; + overlayKeyText.Rotation = value; + } + } + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; private Container keyContainer = null!; @@ -56,6 +67,7 @@ namespace osu.Game.Skinning Text = trigger.Name, Colour = KeyTextColour, Font = OsuFont.Default.With(fixedWidth: true), + Rotation = KeyTextRotation }, } }; @@ -65,7 +77,7 @@ namespace osu.Game.Skinning { base.LoadComplete(); Width = Math.Max(overlayKey.Width, 48 * 0.95f); - Height = Math.Max(overlayKey.Height, 48 * 0.95f); + Height = Math.Max(overlayKey.Height, 46 * 0.95f); } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index e395aefe8a..1847effb3b 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -57,8 +57,17 @@ namespace osu.Game.Skinning { TransitionDuration = key_transition_time, KeyTextColour = keyTextColor, + KeyTextRotation = -Rotation, }; + protected override void Update() + { + base.Update(); + // keep the text are always horizontal + foreach (var child in KeyFlow.Cast()) + child.KeyTextRotation = -Rotation; + } + private Color4 keyTextColor = Color4.White; public Color4 KeyTextColor From f7dc0b65dacd8e98733eb87873f70adb97f2bf56 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:47:49 +0800 Subject: [PATCH 296/670] Clean up the code --- osu.Game/Skinning/LegacyKeyCounter.cs | 13 ++++--------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 4 +--- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index bb1532d75d..333e120024 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.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 System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -20,6 +19,8 @@ namespace osu.Game.Skinning public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; + public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + private float keyTextRotation = 0f; public float KeyTextRotation { @@ -31,8 +32,6 @@ namespace osu.Game.Skinning } } - public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; - private Container keyContainer = null!; private SkinnableSprite overlayKey = null!; @@ -71,13 +70,9 @@ namespace osu.Game.Skinning }, } }; - } - protected override void LoadComplete() - { - base.LoadComplete(); - Width = Math.Max(overlayKey.Width, 48 * 0.95f); - Height = Math.Max(overlayKey.Height, 46 * 0.95f); + // Legacy key counter size + Height = Width = 48 * 0.95f; } protected override void Activate(bool forwardPlayback = true) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 1847effb3b..091691657b 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -16,15 +16,13 @@ namespace osu.Game.Skinning protected override FillFlowContainer KeyFlow { get; } = null!; - private SkinnableSprite overlayBackground = null!; - public LegacyKeyCounterDisplay() { AutoSizeAxes = Axes.Both; AddRangeInternal(new Drawable[] { - overlayBackground = new SkinnableSprite + new SkinnableSprite { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, From dce894108ab0679106b940b46a7cd40dd166674b Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:50:08 +0800 Subject: [PATCH 297/670] Remove unused blending mode --- osu.Game/Skinning/LegacyKeyCounter.cs | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 333e120024..ce757e25e1 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -34,8 +34,6 @@ namespace osu.Game.Skinning private Container keyContainer = null!; - private SkinnableSprite overlayKey = null!; - private OsuSpriteText overlayKeyText = null!; public LegacyKeyCounter(InputTrigger trigger) @@ -50,9 +48,8 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre, Children = new Drawable[] { - overlayKey = new SkinnableSprite + new SkinnableSprite { - Blending = Multiplicative, Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, @@ -89,20 +86,5 @@ namespace osu.Game.Skinning keyContainer.ScaleTo(1f, TransitionDuration); keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); } - - public static BlendingParameters Multiplicative - { - get - { - BlendingParameters result = default(BlendingParameters); - result.Source = BlendingType.SrcAlpha; - result.Destination = BlendingType.OneMinusSrcAlpha; - result.SourceAlpha = BlendingType.One; - result.DestinationAlpha = BlendingType.One; - result.RGBEquation = BlendingEquation.Add; - result.AlphaEquation = BlendingEquation.Add; - return result; - } - } } } From a015fde014aebee865a0ab57736ab340e3fceea9 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 20:53:06 +0800 Subject: [PATCH 298/670] Change the default height to match the stable --- osu.Game/Skinning/LegacySkin.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a890c5ffab..281a9c6053 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -395,9 +395,9 @@ namespace osu.Game.Skinning if (keyCounter != null) { keyCounter.Rotation = 90f; - keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Anchor = Anchor.BottomRight; keyCounter.Origin = Anchor.TopCentre; - keyCounter.Position = new Vector2(0); + keyCounter.Position = new Vector2(0, -340); } }) { @@ -409,7 +409,6 @@ namespace osu.Game.Skinning new LegacySongProgress(), new LegacyHealthDisplay(), new BarHitErrorMeter(), - //new DefaultKeyCounterDisplay(), new LegacyKeyCounterDisplay(), } }; From 9fe369b7f41952dba6b9ddb779f9d792d5e2a867 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 21:08:08 +0800 Subject: [PATCH 299/670] Replace `SkinnableSprite` with `Sprite` --- osu.Game/Skinning/LegacyKeyCounter.cs | 18 ++++++++++++++++-- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 12 ++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index ce757e25e1..08b03b5550 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -22,6 +25,7 @@ namespace osu.Game.Skinning public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; private float keyTextRotation = 0f; + public float KeyTextRotation { get => keyTextRotation; @@ -36,6 +40,8 @@ namespace osu.Game.Skinning private OsuSpriteText overlayKeyText = null!; + private Sprite keySprite = null!; + public LegacyKeyCounter(InputTrigger trigger) : base(trigger) { @@ -48,12 +54,11 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre, Children = new Drawable[] { - new SkinnableSprite + keySprite = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, - SpriteName = { Value = "inputoverlay-key" }, Rotation = -90, }, overlayKeyText = new OsuSpriteText @@ -72,6 +77,15 @@ namespace osu.Game.Skinning Height = Width = 48 * 0.95f; } + [BackgroundDependencyLoader] + private void load(ISkinSource source) + { + Texture? keyTexture = source.GetTexture($"inputoverlay-key"); + + if (keyTexture != null) + keySprite.Texture = keyTexture; + } + protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 091691657b..d26228c1d6 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { @@ -16,17 +18,18 @@ namespace osu.Game.Skinning protected override FillFlowContainer KeyFlow { get; } = null!; + private Sprite backgroundSprite = null!; + public LegacyKeyCounterDisplay() { AutoSizeAxes = Axes.Both; AddRangeInternal(new Drawable[] { - new SkinnableSprite + backgroundSprite = new Sprite { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, - SpriteName = { Value= "inputoverlay-background" }, }, KeyFlow = new FillFlowContainer { @@ -49,6 +52,11 @@ namespace osu.Game.Skinning { KeyTextColor = v.NewValue; }, true); + + Texture? backgroundTexture = source.GetTexture($"inputoverlay-background"); + + if (backgroundTexture != null) + backgroundSprite.Texture = backgroundTexture; } protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) From 989ac56cbbee2c75231aa95c743d3acaf0442f42 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 21:12:55 +0800 Subject: [PATCH 300/670] Fix the return button being squshed --- osu.Game/Skinning/LegacySkin.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 281a9c6053..6e447242da 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -395,9 +395,11 @@ namespace osu.Game.Skinning if (keyCounter != null) { keyCounter.Rotation = 90f; - keyCounter.Anchor = Anchor.BottomRight; + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.TopRight; keyCounter.Origin = Anchor.TopCentre; - keyCounter.Position = new Vector2(0, -340); + keyCounter.X = 0; + keyCounter.Y = container.ToLocalSpace(container.ScreenSpaceDrawQuad.BottomRight).Y - 340; } }) { From c7b110a471f94bb30b4d697124ce8cabdaf90b1e Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 22:11:28 +0800 Subject: [PATCH 301/670] * Fix the default position * Make the font match stable style --- osu.Game/Skinning/LegacyKeyCounter.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 08b03b5550..5640e14dbf 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -67,7 +67,7 @@ namespace osu.Game.Skinning Origin = Anchor.Centre, Text = trigger.Name, Colour = KeyTextColour, - Font = OsuFont.Default.With(fixedWidth: true), + Font = OsuFont.GetFont(size: 20), Rotation = KeyTextRotation }, } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6e447242da..cfa7eb7872 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,7 +15,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -396,10 +395,11 @@ namespace osu.Game.Skinning { keyCounter.Rotation = 90f; // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.TopRight; + keyCounter.Anchor = Anchor.CentreRight; keyCounter.Origin = Anchor.TopCentre; keyCounter.X = 0; - keyCounter.Y = container.ToLocalSpace(container.ScreenSpaceDrawQuad.BottomRight).Y - 340; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; } }) { From c52a993607d50363acabc85b0fcc275a091265f5 Mon Sep 17 00:00:00 2001 From: normalid Date: Tue, 23 Jul 2024 23:35:25 +0800 Subject: [PATCH 302/670] Support custom input overlay color --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 27 +++++++++++++++----- osu.Game/Skinning/LegacyKeyCounter.cs | 22 +++++++++------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 15 ++++++----- osu.Game/Skinning/LegacySkin.cs | 4 ++- osu.Game/Skinning/SkinConfiguration.cs | 4 +++ 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 93af9cf41c..331b84cbf2 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -9,6 +9,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO; using osu.Game.Rulesets.Objects.Legacy; +using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -93,14 +94,8 @@ namespace osu.Game.Beatmaps.Formats return line; } - protected void HandleColours(TModel output, string line, bool allowAlpha) + private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair pair) { - var pair = SplitKeyVal(line); - - bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); - - string[] split = pair.Value.Split(','); - if (split.Length != 3 && split.Length != 4) throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}"); @@ -115,6 +110,17 @@ namespace osu.Game.Beatmaps.Formats { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } + return colour; + } + + protected void HandleColours(TModel output, string line, bool allowAlpha) + { + var pair = SplitKeyVal(line); + + string[] split = pair.Value.Split(','); + Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair); + + bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal); if (isCombo) { @@ -128,6 +134,13 @@ namespace osu.Game.Beatmaps.Formats tHasCustomColours.CustomColours[pair.Key] = colour; } + bool isInputOverlayText = pair.Key.StartsWith(@"InputOverlayText"); + + if (isInputOverlayText) + { + if (!(output is SkinConfiguration tSkinConfiguration)) return; + tSkinConfiguration.InputOverlayText = colour; + } } protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 5640e14dbf..85d5a897fb 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -16,13 +16,19 @@ namespace osu.Game.Skinning { public bool UsesFixedAnchor { get; set; } - public float TransitionDuration { get; set; } = 150f; + public float TransitionDuration { get; set; } = 50f; - public Colour4 KeyTextColour { get; set; } = Colour4.White; + public Colour4 KeyTextColour + { + get => keyTextColour; + set + { + keyTextColour = value; + overlayKeyText.Colour = value; + } + } - public Colour4 KeyDownBackgroundColour { get; set; } = Colour4.Yellow; - - public Colour4 KeyUpBackgroundColour { get; set; } = Colour4.White; + private Colour4 keyTextColour = Colour4.White; private float keyTextRotation = 0f; @@ -66,7 +72,7 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = trigger.Name, - Colour = KeyTextColour, + Colour = keyTextColour, Font = OsuFont.GetFont(size: 20), Rotation = KeyTextRotation }, @@ -89,8 +95,7 @@ namespace osu.Game.Skinning protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.75f, TransitionDuration); - keyContainer.FadeColour(KeyDownBackgroundColour, TransitionDuration); + keyContainer.ScaleTo(0.8f, TransitionDuration); overlayKeyText.Text = CountPresses.Value.ToString(); } @@ -98,7 +103,6 @@ namespace osu.Game.Skinning { base.Deactivate(forwardPlayback); keyContainer.ScaleTo(1f, TransitionDuration); - keyContainer.FadeColour(KeyUpBackgroundColour, TransitionDuration); } } } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index d26228c1d6..a585d93c5a 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -5,7 +5,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Screens.Play.HUD; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -45,10 +44,13 @@ namespace osu.Game.Skinning }); } - [BackgroundDependencyLoader] - private void load(ISkinSource source) + [Resolved] + private ISkinSource source { get; set; } = null!; + + protected override void LoadComplete() { - source.GetConfig("InputOverlayText")?.BindValueChanged(v => + base.LoadComplete(); + source.GetConfig(SkinConfiguration.LegacySetting.InputOverlayText)?.BindValueChanged(v => { KeyTextColor = v.NewValue; }, true); @@ -69,14 +71,15 @@ namespace osu.Game.Skinning protected override void Update() { base.Update(); + // keep the text are always horizontal foreach (var child in KeyFlow.Cast()) child.KeyTextRotation = -Rotation; } - private Color4 keyTextColor = Color4.White; + private Colour4 keyTextColor = Colour4.White; - public Color4 KeyTextColor + public Colour4 KeyTextColor { get => keyTextColor; set diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cfa7eb7872..fa83837214 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,6 +15,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; @@ -309,7 +310,8 @@ namespace osu.Game.Skinning { case SkinConfiguration.LegacySetting.Version: return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); - + case SkinConfiguration.LegacySetting.InputOverlayText: + return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); default: return genericLookup(legacySetting); } diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 937cca0aeb..abfcaff1d8 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -38,8 +39,11 @@ namespace osu.Game.Skinning AnimationFramerate, LayeredHitSounds, AllowSliderBallTint, + InputOverlayText, } + public Colour4? InputOverlayText { get; internal set; } + public static List DefaultComboColours { get; } = new List { new Color4(255, 192, 0, 255), From 661f58a39747ed03ec94bd4df12168ac055d5e2f Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:18:05 +0800 Subject: [PATCH 303/670] Add test coverage --- .../Archives/modified-classic-20240724.osk | Bin 0 -> 518 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk new file mode 100644 index 0000000000000000000000000000000000000000..ed8f3fd3fdd58672e43d31fdf7ab9b0f48e4cac9 GIT binary patch literal 518 zcmWIWW@Zs#U|`^2_?h@4!u!p6tu;X2aj*yjLveOyo?d2NrfSc|yh8>euI1kU+M}1b ztP)zl9JI|ZXKs|(m4|}+8(RO$ZIX5UX~t&P8J`ibU3u=}&wK1|iQN}Jwm|X4lczm# zOE-x4OmR%U=yCMB$RVDhK3P9wlm7gI*%ul3l?a!!L@9MX{Vt)GOx9F<3Ri6VFcfI=a;HK`5m1}>>oOTUvm*3gtmvpu< z_?wpOwzKDE*$1Hbs4sTW-zK1MW&trT5QBV_nU|KYmsOmfxBA)Bpn!l+-e-J6Hw6Wp z($@$z^pmlLBigjx*dKw zectr>)9KF(gyvNqRqd=)o!O~6^JnF}%FLeui@)A!y!L`MJ^k6!)Mray?_9D>b=tEj zKb9<0R|{`)DHTcRno_Ydi!s2Pkx7IBcLV@k3;~TG3Kkvcy3jod)x*HhcnPWt?&Sb) RRyL3{6A%^x>3T*G4*(EG#>4;s literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index f2547b4f5d..039e85bbce 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -65,7 +65,9 @@ namespace osu.Game.Tests.Skins // Covers default rank display "Archives/modified-default-20230809.osk", // Covers legacy rank display - "Archives/modified-classic-20230809.osk" + "Archives/modified-classic-20230809.osk", + // Covcers legacy key counter + "Archives/modified-classic-20240724.osk" }; /// From 95f287104eb85c0165727bcd44e65cf841b6d006 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:24:58 +0800 Subject: [PATCH 304/670] Add visual test seane --- .../Visual/Gameplay/TestSceneKeyCounter.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 2d2b6c3bed..57bfb5fddf 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; using osuTK; using osuTK.Input; @@ -56,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay Anchor = Anchor.Centre, Scale = new Vector2(1, -1) }, + new LegacyKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -89,6 +95,12 @@ namespace osu.Game.Tests.Visual.Gameplay Anchor = Anchor.Centre, Rotation = 90, }, + new LegacyKeyCounterDisplay + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Rotation = 90, + }, } }, } From 395f8424b5aed3837a5aba63d6cb79eb426c3d81 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:30:08 +0800 Subject: [PATCH 305/670] Match the stable animation --- osu.Game/Skinning/LegacyKeyCounter.cs | 2 +- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 85d5a897fb..0637118bd1 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -95,7 +95,7 @@ namespace osu.Game.Skinning protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.8f, TransitionDuration); + keyContainer.ScaleTo(0.75f, TransitionDuration, Easing.OutQuad); overlayKeyText.Text = CountPresses.Value.ToString(); } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index a585d93c5a..adc5d87973 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounterDisplay : KeyCounterDisplay { - private const float key_transition_time = 50; + private const float key_transition_time = 100; protected override FillFlowContainer KeyFlow { get; } = null!; From e2beacb3ddd5e83ab7f7bb2487d13f548f9f5252 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:31:01 +0800 Subject: [PATCH 306/670] Remove logging --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index fa83837214..f754fee077 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -15,7 +15,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; From 56143de2c6ade3e8ffe705bc4dedc6df4acfcb00 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:39:36 +0800 Subject: [PATCH 307/670] Update offset factor --- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index adc5d87973..ccb1c21402 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,6 +8,7 @@ using osu.Game.Screens.Play.HUD; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; namespace osu.Game.Skinning { @@ -35,7 +36,7 @@ namespace osu.Game.Skinning // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay // 24px away from the container, there're 4 counter in legacy, so divide by 4 // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = (24 / 4) * 1.05f, + X = (24 / 4) * 1f, Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, Direction = FillDirection.Horizontal, From b24be96d047d02a258a1ea40569efdd65fe4dae6 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 12:57:30 +0800 Subject: [PATCH 308/670] Fix code quality --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 5 ++- osu.Game/Skinning/LegacyKeyCounter.cs | 10 ++--- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 39 ++++++++++---------- osu.Game/Skinning/LegacySkin.cs | 2 + 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 331b84cbf2..4f11666392 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -110,6 +110,7 @@ namespace osu.Game.Beatmaps.Formats { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } + return colour; } @@ -134,11 +135,13 @@ namespace osu.Game.Beatmaps.Formats tHasCustomColours.CustomColours[pair.Key] = colour; } - bool isInputOverlayText = pair.Key.StartsWith(@"InputOverlayText"); + + bool isInputOverlayText = pair.Key == @"InputOverlayText"; if (isInputOverlayText) { if (!(output is SkinConfiguration tSkinConfiguration)) return; + tSkinConfiguration.InputOverlayText = colour; } } diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 0637118bd1..88ca86c63b 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning private Colour4 keyTextColour = Colour4.White; - private float keyTextRotation = 0f; + private float keyTextRotation; public float KeyTextRotation { @@ -42,11 +42,11 @@ namespace osu.Game.Skinning } } - private Container keyContainer = null!; + private readonly Container keyContainer; - private OsuSpriteText overlayKeyText = null!; + private readonly OsuSpriteText overlayKeyText; - private Sprite keySprite = null!; + private readonly Sprite keySprite; public LegacyKeyCounter(InputTrigger trigger) : base(trigger) @@ -86,7 +86,7 @@ namespace osu.Game.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source) { - Texture? keyTexture = source.GetTexture($"inputoverlay-key"); + Texture? keyTexture = source.GetTexture(@"inputoverlay-key"); if (keyTexture != null) keySprite.Texture = keyTexture; diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index ccb1c21402..651afb788a 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,7 +8,6 @@ using osu.Game.Screens.Play.HUD; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osuTK; namespace osu.Game.Skinning { @@ -16,9 +15,9 @@ namespace osu.Game.Skinning { private const float key_transition_time = 100; - protected override FillFlowContainer KeyFlow { get; } = null!; + protected override FillFlowContainer KeyFlow { get; } - private Sprite backgroundSprite = null!; + private readonly Sprite backgroundSprite; public LegacyKeyCounterDisplay() { @@ -26,22 +25,22 @@ namespace osu.Game.Skinning AddRangeInternal(new Drawable[] { - backgroundSprite = new Sprite - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }, - KeyFlow = new FillFlowContainer - { - // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay - // 24px away from the container, there're 4 counter in legacy, so divide by 4 - // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = (24 / 4) * 1f, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }, + backgroundSprite = new Sprite + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + KeyFlow = new FillFlowContainer + { + // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay + // 24px away from the container, there're 4 counter in legacy, so divide by 4 + // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate + X = 24f / 4f, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }, }); } @@ -56,7 +55,7 @@ namespace osu.Game.Skinning KeyTextColor = v.NewValue; }, true); - Texture? backgroundTexture = source.GetTexture($"inputoverlay-background"); + Texture? backgroundTexture = source.GetTexture(@"inputoverlay-background"); if (backgroundTexture != null) backgroundSprite.Texture = backgroundTexture; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index f754fee077..7a3cc2d785 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -309,8 +309,10 @@ namespace osu.Game.Skinning { case SkinConfiguration.LegacySetting.Version: return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); + case SkinConfiguration.LegacySetting.InputOverlayText: return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); + default: return genericLookup(legacySetting); } From fede6b3657e75391051bc2b8259194999769331a Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 13:09:21 +0800 Subject: [PATCH 309/670] Fix indent problems --- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 651afb788a..44aab407a5 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -28,7 +28,7 @@ namespace osu.Game.Skinning backgroundSprite = new Sprite { Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, + Origin = Anchor.TopLeft, }, KeyFlow = new FillFlowContainer { From 0306ef4096aeb8aafd1eb49c8afefcd338a62f05 Mon Sep 17 00:00:00 2001 From: normalid Date: Wed, 24 Jul 2024 14:13:45 +0800 Subject: [PATCH 310/670] Update test assets --- .../Archives/modified-classic-20240724.osk | Bin 518 -> 1446 bytes .../Skins/SkinDeserialisationTest.cs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk index ed8f3fd3fdd58672e43d31fdf7ab9b0f48e4cac9..29a06abf1d00fae7cb73a2cfb868f96d2be2bcca 100644 GIT binary patch literal 1446 zcmWIWW@Zs#U|`^2Xes;=Ay6+@eI3YK!Uz&!U?|Sc%+t%v%e>le=*Mg*;!^Fs@32a! z(#D%-g)}dfg_MLi2rvB+`CnCRTYXCP)>DEznB5&7F^J4zi&}TU&EYufrL!6H=9$Fj zzfbMFZM#_bdCG=|jB8uYC?+ppd9}IX)Hc=UeG17p63Sba)G=K?E}Zw*F=^j{zDpKf z4(YP&pL#y2n94k~oV?J7d#%}Z8H-7`vr3JP&h}^Bvhq5)E%?ltz+^=dFJBG$-lFRg;y_OHoLh~!*rv{evXuijJUG6x=Oi_L&ocOs2HwSG2WW~n&aAAQS^2U`Z)IlZQcG34l`lI_PE`n$Wu2j<8n-Tr0o5PoiSe~TObiTy{0t0Y zK!5lqX6AW>x;W?O7Ubup=9LtKJ#;rLHvh7LNUi%Ldzacg-Q_1Yv99D_%-Y2ec#&6b znP$+Vt)I$6u0jGk!Ik@M`-cG`F~Reg3(VTjk^S7tOiGa8tIMwPuR> zs;|c?H5|B?J010jc#y?vA)R(IPABhRk?zvsPmO0AdX~p{=>>n144S6%D@$zkzEevX zbF62t*!EdApjfqfiq8CPm(RDJ6s!^|YWCSOH&BjyUQN@{)>%z&PR4YERWJYKk(rm% z^)~GKk-e^6Q&bB!GHt){IB#A4sSTy;KApN|k;tUwy<UYBLWhWtNNo>#})T#J}&#g8QGVXV~cOPIPi!DQR%bYjJm-SM=-j7$1SI zCE?elYotG_H}wm*eu(Gt^OOj=%F%p__4^*LgQp9eS?v8M$xN@WQa1Ts`-p8yS+?lE zCs&p&xw3jv{o%bF!c)GTzjn%1cwIhg^tR#!-B#=^lU{O!b}jfSc=l4^-(aWRaXZ#0 zKA61OV8Oij*DnJe`hI_4v+BNT9kcsUff)uj6P|9Xx7B#Hk5|U?{f3}-8w9h|ITTDK zBTG+yC~|1Kv&T8)2kV=I3rcuHT>SGbO;%&G2x>In(}~50+)zN#J|xF?Z$ejAhTZr5t(IpwoJ4#}P5N zjx?U-4HM0}pMEbp?bviwVcV0>^P{g-zSchXkv-zL^1k{z|^M;#C$*;kds)MmYJH9f|Q+# zG`)2EeNK6PJ>z{kd{fX0HcqD2&)wdQdtO8cY@arLwHPpEGct)V;4Ywm217t2h=LVY z=(^BzHdGG-L*rklE_gmi*NUE*5L%A{%Sh~53*8L#pg@@6&4L~<0p6@^AbA!b{0OAy J1643E008f1L+Jnj literal 518 zcmWIWW@Zs#U|`^2_?h@4!u!p6tu;X2aj*yjLveOyo?d2NrfSc|yh8>euI1kU+M}1b ztP)zl9JI|ZXKs|(m4|}+8(RO$ZIX5UX~t&P8J`ibU3u=}&wK1|iQN}Jwm|X4lczm# zOE-x4OmR%U=yCMB$RVDhK3P9wlm7gI*%ul3l?a!!L@9MX{Vt)GOx9F<3Ri6VFcfI=a;HK`5m1}>>oOTUvm*3gtmvpu< z_?wpOwzKDE*$1Hbs4sTW-zK1MW&trT5QBV_nU|KYmsOmfxBA)Bpn!l+-e-J6Hw6Wp z($@$z^pmlLBigjx*dKw zectr>)9KF(gyvNqRqd=)o!O~6^JnF}%FLeui@)A!y!L`MJ^k6!)Mray?_9D>b=tEj zKb9<0R|{`)DHTcRno_Ydi!s2Pkx7IBcLV@k3;~TG3Kkvcy3jod)x*HhcnPWt?&Sb) RRyL3{6A%^x>3T*G4*(EG#>4;s diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 039e85bbce..534d47d617 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Skins "Archives/modified-default-20230809.osk", // Covers legacy rank display "Archives/modified-classic-20230809.osk", - // Covcers legacy key counter + // Covers legacy key counter "Archives/modified-classic-20240724.osk" }; From bf4bf4d39e126e67d3125a4e03a04c7fda2af677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 08:54:30 +0200 Subject: [PATCH 311/670] Fill daily challenge top 50 position numbers client-side Only doing this client-side, because doing this server-side is expensive: https://github.com/ppy/osu-web/pull/11354#discussion_r1689224285 --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 2b2c3a5e1f..8ba490b14d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -138,9 +138,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } else { - LoadComponentsAsync(best.Select(s => new LeaderboardScoreV2(s, sheared: false) + LoadComponentsAsync(best.Select((s, index) => new LeaderboardScoreV2(s, sheared: false) { - Rank = s.Position, + Rank = index + 1, IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), }), loaded => From 788b70469d855f46d08adce40bd5ab983d381bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 09:15:32 +0200 Subject: [PATCH 312/670] Exit daily challenge screen when going offline This sort of thing is bound to happen when rewriting screens from scratch without invoking abstract eldritch entities sometimes. Damned if you do, damned if you don't... --- .../DailyChallenge/DailyChallenge.cs | 28 +++++++++++++++++++ .../Screens/OnlinePlay/OnlinePlayScreen.cs | 1 + 2 files changed, 29 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 2d58b3b82c..2101444728 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -21,6 +21,7 @@ using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; @@ -48,6 +49,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge /// private readonly Bindable> userMods = new Bindable>(Array.Empty()); + private readonly IBindable apiState = new Bindable(); + private OnlinePlayScreenWaveContainer waves = null!; private DailyChallengeLeaderboard leaderboard = null!; private RoomModSelectOverlay userModsSelectOverlay = null!; @@ -84,6 +87,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private UserLookupCache userLookupCache { get; set; } = null!; + [Resolved] + protected IAPIProvider API { get; private set; } = null!; + public override bool DisallowExternalBeatmapRulesetChanges => true; public DailyChallenge(Room room) @@ -358,6 +364,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); userModsSelectOverlay.SelectedItem.Value = playlistItem; userMods.BindValueChanged(_ => Scheduler.AddOnce(updateMods), true); + + apiState.BindTo(API.State); + apiState.BindValueChanged(onlineStateChanged, true); } private void trySetDailyChallengeBeatmap() @@ -368,6 +377,25 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge applyLoopingToTrack(); } + private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => + { + if (state.NewValue != APIState.Online) + Schedule(forcefullyExit); + }); + + private void forcefullyExit() + { + Logger.Log($"{this} forcefully exiting due to loss of API connection"); + + // This is temporary since we don't currently have a way to force screens to be exited + // See also: `OnlinePlayScreen.forcefullyExit()` + if (this.IsCurrentScreen()) + { + while (this.IsCurrentScreen()) + this.Exit(); + } + } + public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index 9b6284fb89..1b7041c9bb 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -99,6 +99,7 @@ namespace osu.Game.Screens.OnlinePlay Logger.Log($"{this} forcefully exiting due to loss of API connection"); // This is temporary since we don't currently have a way to force screens to be exited + // See also: `DailyChallenge.forcefullyExit()` if (this.IsCurrentScreen()) { while (this.IsCurrentScreen()) From 55382a4ba6edea3f1d26227bd125f8db7f3c8c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 12:08:12 +0200 Subject: [PATCH 313/670] Add test coverage for expected sample popover behaviour --- .../TestSceneHitObjectSampleAdjustments.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs index 558d8dce94..75a68237c8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectSampleAdjustments.cs @@ -402,6 +402,70 @@ namespace osu.Game.Tests.Visual.Editing void checkPlacementSample(string expected) => AddAssert($"Placement sample is {expected}", () => EditorBeatmap.PlacementObject.Value.Samples.First().Bank, () => Is.EqualTo(expected)); } + [Test] + public void PopoverForMultipleSelectionChangesAllSamples() + { + AddStep("add slider", () => + { + EditorBeatmap.Add(new Slider + { + Position = new Vector2(256, 256), + StartTime = 1000, + Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }), + Samples = + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL) + }, + NodeSamples = new List> + { + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM), + new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM), + }, + new List + { + new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT), + new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT), + }, + } + }); + }); + AddStep("select everything", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + + clickSamplePiece(0); + + setBankViaPopover(HitSampleInfo.BANK_DRUM); + samplePopoverHasSingleBank(HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_DRUM); + hitObjectHasSampleNormalBank(2, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 0, HitSampleInfo.BANK_DRUM); + hitObjectNodeHasSampleNormalBank(2, 1, HitSampleInfo.BANK_DRUM); + + setVolumeViaPopover(30); + samplePopoverHasSingleVolume(30); + hitObjectHasSampleVolume(0, 30); + hitObjectHasSampleVolume(1, 30); + hitObjectHasSampleVolume(2, 30); + hitObjectNodeHasSampleVolume(2, 0, 30); + hitObjectNodeHasSampleVolume(2, 1, 30); + + toggleAdditionViaPopover(0); + hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectHasSamples(2, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_CLAP, HitSampleInfo.HIT_WHISTLE); + hitObjectNodeHasSamples(2, 1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_WHISTLE); + + setAdditionBankViaPopover(HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_SOFT); + hitObjectHasSampleAdditionBank(2, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 0, HitSampleInfo.BANK_SOFT); + hitObjectNodeHasSampleAdditionBank(2, 1, HitSampleInfo.BANK_SOFT); + } + [Test] public void TestHotkeysAffectNodeSamples() { From 1ed7e4b075bafe9ccf2e33ef252e272c61bb7d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 14:34:48 +0200 Subject: [PATCH 314/670] Make sample popover change properties of all samples in multiple selection Closes https://github.com/ppy/osu/issues/28916. The previous behaviour *may* have been intended, but it was honestly quite baffling. This seems like a saner variant. --- .../Timeline/NodeSamplePointPiece.cs | 8 +-- .../Components/Timeline/SamplePointPiece.cs | 51 +++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs index ae3838bc41..9cc1268db7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/NodeSamplePointPiece.cs @@ -34,12 +34,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly int nodeIndex; - protected override IList GetRelevantSamples(HitObject ho) + protected override IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) { - if (ho is not IHasRepeats hasRepeats) - return ho.Samples; + if (hitObjects.Length > 1 || hitObjects[0] is not IHasRepeats hasRepeats) + return base.GetRelevantSamples(hitObjects); - return nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : ho.Samples; + return [(hitObjects[0], nodeIndex < hasRepeats.NodeSamples.Count ? hasRepeats.NodeSamples[nodeIndex] : hitObjects[0].Samples)]; } public NodeSampleEditPopover(HitObject hitObject, int nodeIndex) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index 8c7603021a..8bfb0a3358 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -21,6 +21,7 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; @@ -106,15 +107,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private FillFlowContainer togglesCollection = null!; private HitObject[] relevantObjects = null!; - private IList[] allRelevantSamples = null!; + private (HitObject hitObject, IList samples)[] allRelevantSamples = null!; /// /// Gets the sub-set of samples relevant to this sample point piece. /// For example, to edit node samples this should return the samples at the index of the node. /// - /// The hit object to get the relevant samples from. + /// The hit objects to get the relevant samples from. /// The relevant list of samples. - protected virtual IList GetRelevantSamples(HitObject ho) => ho.Samples; + protected virtual IEnumerable<(HitObject hitObject, IList samples)> GetRelevantSamples(HitObject[] hitObjects) + { + if (hitObjects.Length == 1) + { + yield return (hitObjects[0], hitObjects[0].Samples); + + yield break; + } + + foreach (var ho in hitObjects) + { + yield return (ho, ho.Samples); + + if (ho is IHasRepeats hasRepeats) + { + foreach (var node in hasRepeats.NodeSamples) + yield return (ho, node); + } + } + } [Resolved(canBeNull: true)] private EditorBeatmap beatmap { get; set; } = null!; @@ -172,7 +192,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // if the piece belongs to a currently selected object, assume that the user wants to change all selected objects. // if the piece belongs to an unselected object, operate on that object alone, independently of the selection. relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).ToArray(); - allRelevantSamples = relevantObjects.Select(GetRelevantSamples).ToArray(); + allRelevantSamples = GetRelevantSamples(relevantObjects).ToArray(); // even if there are multiple objects selected, we can still display sample volume or bank if they all have the same value. int? commonVolume = getCommonVolume(); @@ -214,9 +234,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) })); } - private string? getCommonBank() => allRelevantSamples.Select(GetBankValue).Distinct().Count() == 1 ? GetBankValue(allRelevantSamples.First()) : null; - private string? getCommonAdditionBank() => allRelevantSamples.Select(GetAdditionBankValue).Where(o => o is not null).Distinct().Count() == 1 ? GetAdditionBankValue(allRelevantSamples.First()) : null; - private int? getCommonVolume() => allRelevantSamples.Select(GetVolumeValue).Distinct().Count() == 1 ? GetVolumeValue(allRelevantSamples.First()) : null; + private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1 + ? GetBankValue(allRelevantSamples.First().samples) + : null; + + private string? getCommonAdditionBank() + { + string[] additionBanks = allRelevantSamples.Select(h => GetAdditionBankValue(h.samples)).Where(o => o is not null).Cast().Distinct().ToArray(); + return additionBanks.Length == 1 ? additionBanks[0] : null; + } + + private int? getCommonVolume() => allRelevantSamples.Select(h => GetVolumeValue(h.samples)).Distinct().Count() == 1 + ? GetVolumeValue(allRelevantSamples.First().samples) + : null; private void updatePrimaryBankState() { @@ -231,7 +261,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline additionBank.PlaceholderText = string.IsNullOrEmpty(commonAdditionBank) ? "(multiple)" : string.Empty; additionBank.Current.Value = commonAdditionBank; - bool anyAdditions = allRelevantSamples.Any(o => o.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); + bool anyAdditions = allRelevantSamples.Any(o => o.samples.Any(s => s.Name != HitSampleInfo.HIT_NORMAL)); if (anyAdditions) additionBank.Show(); else @@ -247,9 +277,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { beatmap.BeginChange(); - foreach (var relevantHitObject in relevantObjects) + foreach (var (relevantHitObject, relevantSamples) in GetRelevantSamples(relevantObjects)) { - var relevantSamples = GetRelevantSamples(relevantHitObject); updateAction(relevantHitObject, relevantSamples); beatmap.Update(relevantHitObject); } @@ -333,7 +362,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { foreach ((string sampleName, var bindable) in selectionSampleStates) { - bindable.Value = SelectionHandler.GetStateFromSelection(relevantObjects, h => GetRelevantSamples(h).Any(s => s.Name == sampleName)); + bindable.Value = SelectionHandler.GetStateFromSelection(GetRelevantSamples(relevantObjects), h => h.samples.Any(s => s.Name == sampleName)); } } From ace5071d888eb644371b9255ea5f70e265a820c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:25:51 +0200 Subject: [PATCH 315/670] Add better test scene --- .../Gameplay/TestSceneSkinnableKeyCounter.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs new file mode 100644 index 0000000000..07c39793d2 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.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.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneSkinnableKeyCounter : SkinnableHUDComponentTestScene + { + [Cached] + private readonly InputCountController controller = new InputCountController(); + + public override void SetUpSteps() + { + AddStep("create dependencies", () => + { + Add(controller); + controller.Add(new KeyCounterKeyboardTrigger(Key.Z)); + controller.Add(new KeyCounterKeyboardTrigger(Key.X)); + controller.Add(new KeyCounterKeyboardTrigger(Key.C)); + controller.Add(new KeyCounterKeyboardTrigger(Key.V)); + + foreach (var trigger in controller.Triggers) + Add(trigger); + }); + base.SetUpSteps(); + } + + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + + protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay(); + + protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); //{ Rotation = 90, }; + } +} From 087dd759be973612cd4c734cb3948ecc115465cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:26:16 +0200 Subject: [PATCH 316/670] Adjust layout to ballpark-match stable I dunno what the wiki is claiming with the "24px" figure or why but I'm not playing conversion games either. Dimensions ballparked via screenshots captured at x768 resolution. Also removes a weird homebrew method to keep the text upright. There is one canonical way to do this, namely `UprightAspectMaintainingContainer`. And the other key counters were already using it. --- .../Gameplay/TestSceneSkinnableKeyCounter.cs | 2 +- osu.Game/Skinning/LegacyKeyCounter.cs | 36 ++++++++----------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 28 ++++++--------- osu.Game/Skinning/LegacySkin.cs | 3 +- 4 files changed, 26 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs index 07c39793d2..098f8e3246 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableKeyCounter.cs @@ -37,6 +37,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay(); - protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); //{ Rotation = 90, }; + protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay(); } } diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 88ca86c63b..d5ba14e484 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Play.HUD; @@ -30,18 +31,6 @@ namespace osu.Game.Skinning private Colour4 keyTextColour = Colour4.White; - private float keyTextRotation; - - public float KeyTextRotation - { - get => keyTextRotation; - set - { - keyTextRotation = value; - overlayKeyText.Rotation = value; - } - } - private readonly Container keyContainer; private readonly OsuSpriteText overlayKeyText; @@ -55,7 +44,7 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre; Child = keyContainer = new Container { - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Origin = Anchor.Centre, Anchor = Anchor.Centre, Children = new Drawable[] @@ -64,23 +53,26 @@ namespace osu.Game.Skinning { Anchor = Anchor.Centre, Origin = Anchor.Centre, - BypassAutoSizeAxes = Axes.Both, - Rotation = -90, }, - overlayKeyText = new OsuSpriteText + new UprightAspectMaintainingContainer { + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = trigger.Name, - Colour = keyTextColour, - Font = OsuFont.GetFont(size: 20), - Rotation = KeyTextRotation + Child = overlayKeyText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = trigger.Name, + Colour = keyTextColour, + Font = OsuFont.GetFont(size: 20), + }, }, } }; - // Legacy key counter size - Height = Width = 48 * 0.95f; + // matches longest dimension of default skin asset + Height = Width = 46; } [BackgroundDependencyLoader] diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 44aab407a5..abfe607aab 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -8,6 +8,7 @@ using osu.Game.Screens.Play.HUD; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; namespace osu.Game.Skinning { @@ -27,18 +28,19 @@ namespace osu.Game.Skinning { backgroundSprite = new Sprite { - Anchor = Anchor.TopLeft, + Anchor = Anchor.TopRight, Origin = Anchor.TopLeft, + Scale = new Vector2(1.05f, 1), + Rotation = 90, }, KeyFlow = new FillFlowContainer { - // https://osu.ppy.sh/wiki/en/Skinning/Interface#input-overlay - // 24px away from the container, there're 4 counter in legacy, so divide by 4 - // "inputoverlay-background.png" are 1.05x in-game. so *1.05f to the X coordinate - X = 24f / 4f, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Horizontal, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + X = -1.5f, + Y = 7, + Spacing = new Vector2(1.8f), + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, }, }); @@ -65,18 +67,8 @@ namespace osu.Game.Skinning { TransitionDuration = key_transition_time, KeyTextColour = keyTextColor, - KeyTextRotation = -Rotation, }; - protected override void Update() - { - base.Update(); - - // keep the text are always horizontal - foreach (var child in KeyFlow.Cast()) - child.KeyTextRotation = -Rotation; - } - private Colour4 keyTextColor = Colour4.White; public Colour4 KeyTextColor diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 7a3cc2d785..191ca04153 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -396,10 +396,9 @@ namespace osu.Game.Skinning if (keyCounter != null) { - keyCounter.Rotation = 90f; // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.TopCentre; + keyCounter.Origin = Anchor.CentreRight; keyCounter.X = 0; // 340px is the default height inherit from stable keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; From 3c28c116ca76e153503da8ed8465727b92ee383f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 14:49:23 +0200 Subject: [PATCH 317/670] Simplify input overlay text colour decode (and fix incorrect default) --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 10 ---------- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 3 --- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 4f11666392..30a78a16ed 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -9,7 +9,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO; using osu.Game.Rulesets.Objects.Legacy; -using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Beatmaps.Formats @@ -135,15 +134,6 @@ namespace osu.Game.Beatmaps.Formats tHasCustomColours.CustomColours[pair.Key] = colour; } - - bool isInputOverlayText = pair.Key == @"InputOverlayText"; - - if (isInputOverlayText) - { - if (!(output is SkinConfiguration tSkinConfiguration)) return; - - tSkinConfiguration.InputOverlayText = colour; - } } protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 191ca04153..64965874a5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -311,7 +311,7 @@ namespace osu.Game.Skinning return SkinUtils.As(new Bindable(Configuration.LegacyVersion ?? SkinConfiguration.LATEST_VERSION)); case SkinConfiguration.LegacySetting.InputOverlayText: - return SkinUtils.As(new Bindable(Configuration.InputOverlayText ?? Colour4.White)); + return SkinUtils.As(new Bindable(Configuration.CustomColours.TryGetValue(@"InputOverlayText", out var colour) ? colour : Colour4.Black)); default: return genericLookup(legacySetting); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index abfcaff1d8..a657a667eb 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Game.Beatmaps.Formats; using osuTK.Graphics; @@ -42,8 +41,6 @@ namespace osu.Game.Skinning InputOverlayText, } - public Colour4? InputOverlayText { get; internal set; } - public static List DefaultComboColours { get; } = new List { new Color4(255, 192, 0, 255), From 26395bd443dd5ca13f641c9486180a0d8cc74d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 15:07:15 +0200 Subject: [PATCH 318/670] Adjust animations further to match stable --- osu.Game/Skinning/LegacyKeyCounter.cs | 25 ++++++++++---------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 13 ++++++---- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index d5ba14e484..8a182de9b7 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -15,26 +15,24 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounter : KeyCounter { - public bool UsesFixedAnchor { get; set; } + private const float transition_duration = 160; - public float TransitionDuration { get; set; } = 50f; + public Colour4 ActiveColour { get; set; } - public Colour4 KeyTextColour + private Colour4 textColour; + + public Colour4 TextColour { - get => keyTextColour; + get => textColour; set { - keyTextColour = value; + textColour = value; overlayKeyText.Colour = value; } } - private Colour4 keyTextColour = Colour4.White; - private readonly Container keyContainer; - private readonly OsuSpriteText overlayKeyText; - private readonly Sprite keySprite; public LegacyKeyCounter(InputTrigger trigger) @@ -64,7 +62,7 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = trigger.Name, - Colour = keyTextColour, + Colour = textColour, Font = OsuFont.GetFont(size: 20), }, }, @@ -87,14 +85,17 @@ namespace osu.Game.Skinning protected override void Activate(bool forwardPlayback = true) { base.Activate(forwardPlayback); - keyContainer.ScaleTo(0.75f, TransitionDuration, Easing.OutQuad); + keyContainer.ScaleTo(0.75f, transition_duration, Easing.Out); + keySprite.Colour = ActiveColour; overlayKeyText.Text = CountPresses.Value.ToString(); + overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.Bold); } protected override void Deactivate(bool forwardPlayback = true) { base.Deactivate(forwardPlayback); - keyContainer.ScaleTo(1f, TransitionDuration); + keyContainer.ScaleTo(1f, transition_duration, Easing.Out); + keySprite.Colour = Colour4.White; } } } diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index abfe607aab..8c652085e4 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -14,7 +14,8 @@ namespace osu.Game.Skinning { public partial class LegacyKeyCounterDisplay : KeyCounterDisplay { - private const float key_transition_time = 100; + private static readonly Colour4 active_colour_top = Colour4.FromHex(@"#ffde00"); + private static readonly Colour4 active_colour_bottom = Colour4.FromHex(@"#f8009e"); protected override FillFlowContainer KeyFlow { get; } @@ -61,12 +62,16 @@ namespace osu.Game.Skinning if (backgroundTexture != null) backgroundSprite.Texture = backgroundTexture; + + for (int i = 0; i < KeyFlow.Count; ++i) + { + ((LegacyKeyCounter)KeyFlow[i]).ActiveColour = i < 2 ? active_colour_top : active_colour_bottom; + } } protected override KeyCounter CreateCounter(InputTrigger trigger) => new LegacyKeyCounter(trigger) { - TransitionDuration = key_transition_time, - KeyTextColour = keyTextColor, + TextColour = keyTextColor, }; private Colour4 keyTextColor = Colour4.White; @@ -80,7 +85,7 @@ namespace osu.Game.Skinning { keyTextColor = value; foreach (var child in KeyFlow.Cast()) - child.KeyTextColour = value; + child.TextColour = value; } } } From c3dae81935a18eb62ebbfa7fdc2c36af5fd9fe2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 15:41:20 +0200 Subject: [PATCH 319/670] Only add legacy key overlay to osu! and catch HUD layers --- osu.Game/Skinning/LegacySkin.cs | 138 +++++++++++++++++++------------- 1 file changed, 81 insertions(+), 57 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 64965874a5..4ca0e3cac0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,70 +356,16 @@ namespace osu.Game.Skinning switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return new DefaultSkinComponentsContainer(container => - { - var score = container.OfType().FirstOrDefault(); - var accuracy = container.OfType().FirstOrDefault(); + return createDefaultHUDComponents(containerLookup); - if (score != null && accuracy != null) - { - accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; - } - - var songProgress = container.OfType().FirstOrDefault(); - - if (songProgress != null && accuracy != null) - { - songProgress.Anchor = Anchor.TopRight; - songProgress.Origin = Anchor.CentreRight; - songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; - songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); - } - - var hitError = container.OfType().FirstOrDefault(); - - if (hitError != null) - { - hitError.Anchor = Anchor.BottomCentre; - hitError.Origin = Anchor.CentreLeft; - hitError.Rotation = -90; - } - - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyScoreCounter(), - new LegacyAccuracyCounter(), - new LegacySongProgress(), - new LegacyHealthDisplay(), - new BarHitErrorMeter(), - new LegacyKeyCounterDisplay(), - } - }; + default: + return null; } - return null; - case GameplaySkinComponentLookup resultComponent: // kind of wasteful that we throw this away, but should do for now. @@ -442,6 +388,84 @@ namespace osu.Game.Skinning return null; } + private static DefaultSkinComponentsContainer? createDefaultHUDComponents(SkinComponentsContainerLookup containerLookup) + { + switch (containerLookup.Ruleset?.ShortName) + { + case null: + { + return new DefaultSkinComponentsContainer(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); + + if (score != null && accuracy != null) + { + accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; + } + + var songProgress = container.OfType().FirstOrDefault(); + + if (songProgress != null && accuracy != null) + { + songProgress.Anchor = Anchor.TopRight; + songProgress.Origin = Anchor.CentreRight; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; + songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); + } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.BottomCentre; + hitError.Origin = Anchor.CentreLeft; + hitError.Rotation = -90; + } + }) + { + Children = new Drawable[] + { + new LegacyComboCounter(), + new LegacyScoreCounter(), + new LegacyAccuracyCounter(), + new LegacySongProgress(), + new LegacyHealthDisplay(), + new BarHitErrorMeter(), + } + }; + } + + case @"osu": + case @"fruits": + { + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); + + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } + + default: + return null; + } + } + private Texture? getParticleTexture(HitResult result) { switch (result) From 12a9086aa31127b4b81f5a02cbcfe190b4159b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 24 Jul 2024 18:30:18 +0200 Subject: [PATCH 320/670] Fix test failure After the legacy key counter was moved to ruleset-specific component containers, `TestSceneSkinnableHUDOverlay` no longer had a key counter, because it wasn't creating a ruleset-specific HUD component container due to https://github.com/ppy/osu/blob/4983e5f33ed11ba3777e53face6271066ba01ab9/osu.Game/Screens/Play/HUDOverlay.cs#L131-L133 Therefore, to fix, do just enough persuading to make it create one. --- .../Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index 4cb0d5c0ff..d1e224a910 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -16,12 +16,13 @@ using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Tests.Gameplay; -using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -91,10 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay { SetContents(_ => { - hudOverlay = new HUDOverlay(null, Array.Empty()); - - // Add any key just to display the key counter visually. - hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space)); + hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty()); action?.Invoke(hudOverlay); From 6645dac71d418499b1d758e06fcf859b4329005f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Jul 2024 23:19:04 +0300 Subject: [PATCH 321/670] Fix dragging number boxes overwritten by select-all-on-focus feature --- .../UserInterface/TestSceneOsuTextBox.cs | 18 ++++++++++++++++++ osu.Game/Graphics/UserInterface/OsuTextBox.cs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index 921c5bbbfa..435fe2f7a2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -76,6 +76,24 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddAssert("text selected", () => numberBoxes.First().SelectedText == "987654321"); + + AddStep("click away", () => + { + InputManager.MoveMouseTo(Vector2.Zero); + InputManager.Click(MouseButton.Left); + }); + + Drawable textContainer = null!; + + AddStep("move mouse to end of text", () => + { + textContainer = numberBoxes.First().ChildrenOfType().ElementAt(1); + InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.TopRight); + }); + AddStep("hold mouse", () => InputManager.PressButton(MouseButton.Left)); + AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2, 0))); + AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("half text selected", () => numberBoxes.First().SelectedText == "54321"); } private void clearTextboxes(IEnumerable textBoxes) => AddStep("clear textbox", () => textBoxes.ForEach(textBox => textBox.Text = null)); diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 90a000d441..6388f56f61 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -261,7 +261,8 @@ namespace osu.Game.Graphics.UserInterface base.OnFocus(e); - if (SelectAllOnFocus) + // we may become focused from an ongoing drag operation, we don't want to overwrite selection in that case. + if (SelectAllOnFocus && string.IsNullOrEmpty(SelectedText)) SelectAll(); } From b3e3bf7ceca3b67663b38036ba3002cc4e6dc68b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Jul 2024 23:26:23 +0300 Subject: [PATCH 322/670] Add lenience to avoid floating point errors --- osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs index 435fe2f7a2..abad7e775c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.TopRight); }); AddStep("hold mouse", () => InputManager.PressButton(MouseButton.Left)); - AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2, 0))); + AddStep("drag to half", () => InputManager.MoveMouseTo(textContainer.ScreenSpaceDrawQuad.BottomRight - new Vector2(textContainer.ScreenSpaceDrawQuad.Width / 2 + 1f, 0))); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("half text selected", () => numberBoxes.First().SelectedText == "54321"); } From 4cc07badbd7f3c68b39456d51bfd98d0dd093b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 09:05:58 +0900 Subject: [PATCH 323/670] Disable macOS test runs for now We are seeing update frames run as little as [once per second](https://github.com/ppy/osu/blob/aa4d16bdb873d7296b899cee7b7491ffdf5cd6ab/osu.Game/Overlays/BeatmapListingOverlay.cs#L141). Until we can ascertain why this is happening, let's reduce developer stress by not running macOS tests for now. --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc1cb6c186..ba65cfa33a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,8 @@ jobs: matrix: os: - { prettyname: Windows, fullname: windows-latest } - - { prettyname: macOS, fullname: macos-latest } + # macOS runner performance has gotten unbearably slow so let's turn them off temporarily. + # - { prettyname: macOS, fullname: macos-latest } - { prettyname: Linux, fullname: ubuntu-latest } threadingMode: ['SingleThread', 'MultiThreaded'] timeout-minutes: 120 From d63335082ec4be44a40cc4386852bf876dba501c Mon Sep 17 00:00:00 2001 From: Cyrus Yip Date: Wed, 24 Jul 2024 18:24:52 -0700 Subject: [PATCH 324/670] fix link to good first issues --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fe6b6fb4d..ebe1e08074 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change. -The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience. +The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience. In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive. From a696e3c2612bd079ba061e6b7715a1757dd0fbe2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Jul 2024 10:44:44 +0900 Subject: [PATCH 325/670] Add reference to android project --- .../osu.Game.Rulesets.Taiko.Tests.Android.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index ee973e8544..88aa137797 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -19,6 +19,7 @@ + From 9ec687caab0178cf2cb17ea9872a548bbcda70d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 12:55:45 +0900 Subject: [PATCH 326/670] Avoid reloading the daily challenge leaderboard when already requested --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 8ba490b14d..4c4622bba3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -118,9 +118,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge RefetchScores(); } + private IndexPlaylistScoresRequest? request; + public void RefetchScores() { - var request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); + if (request?.CompletionState == APIRequestCompletionState.Waiting) + return; + + request = new IndexPlaylistScoresRequest(room.RoomID.Value!.Value, playlistItem.ID); request.Success += req => Schedule(() => { From aac98ab6b25fd5a6fe5ccf57e219eb5b4dc3431f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 12:58:32 +0900 Subject: [PATCH 327/670] Debounce leaderboard refetches to stop excessive operations after returning from gameplay --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 2101444728..aab0458275 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -349,7 +349,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge feed.AddNewScore(ev); if (e.NewRank <= 50) - Schedule(() => leaderboard.RefetchScores()); + Scheduler.AddOnce(() => leaderboard.RefetchScores()); }); }); } @@ -486,7 +486,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge sampleStart?.Play(); this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) { - Exited = () => leaderboard.RefetchScores() + Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); } From 0182f3d7c3e52fd63f9267148498ee069d5644f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:39:58 +0300 Subject: [PATCH 328/670] Add failing test case --- .../DailyChallenge/TestSceneDailyChallenge.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index cd09a1d20f..0e0927a678 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,9 +4,12 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.SelectV2.Leaderboards; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; @@ -14,10 +17,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { - [Test] - public void TestDailyChallenge() + [SetUpSteps] + public override void SetUpSteps() { - var room = new Room + base.SetUpSteps(); + + Room room = null!; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room = new Room { RoomID = { Value = 1234 }, Name = { Value = "Daily Challenge: June 4, 2024" }, @@ -31,10 +38,22 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, Category = { Value = RoomCategory.DailyChallenge } - }; + }))); - AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } + + [Test] + public void TestDailyChallenge() + { + } + + [Test] + public void TestScoreNavigation() + { + AddStep("click on score", () => this.ChildrenOfType().First().TriggerClick()); + AddUntilStep("wait for load", () => Stack.CurrentScreen is ResultsScreen results && results.IsLoaded); + AddAssert("replay download button exists", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); + } } } From dad8e28446fe82940d0fd5cbb8e99f63d083c5de Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:40:17 +0300 Subject: [PATCH 329/670] Fix replay download button not added when no score is selected initially --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 10 +++++----- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index df5f9c7a8a..aac29ad269 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking { public partial class ReplayDownloadButton : CompositeDrawable, IKeyBindingHandler { - public readonly Bindable Score = new Bindable(); + public readonly Bindable Score = new Bindable(); protected readonly Bindable State = new Bindable(); @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Ranking } } - public ReplayDownloadButton(ScoreInfo score) + public ReplayDownloadButton(ScoreInfo? score) { Score.Value = score; Size = new Vector2(50, 30); @@ -67,11 +67,11 @@ namespace osu.Game.Screens.Ranking switch (State.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(Score.Value, ScorePresentType.Gameplay); + game?.PresentScore(Score.Value!, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: - scoreDownloader.Download(Score.Value); + scoreDownloader.Download(Score.Value!); break; case DownloadState.Importing: @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Ranking { if (state.NewValue != DownloadState.LocallyAvailable) return; - scoreManager.Export(Score.Value); + scoreManager.Export(Score.Value!); State.ValueChanged -= exportWhenReady; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db53..0793697833 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -179,11 +179,11 @@ namespace osu.Game.Screens.Ranking Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0); } - if (SelectedScore.Value != null && AllowWatchingReplay) + if (AllowWatchingReplay) { buttons.Add(new ReplayDownloadButton(SelectedScore.Value) { - Score = { BindTarget = SelectedScore! }, + Score = { BindTarget = SelectedScore }, Width = 300 }); } From bba151a776bb910c0db8514aa66564130e4a6ead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:24:04 +0900 Subject: [PATCH 330/670] Make event feed test show more realistic content automatically --- .../TestSceneDailyChallengeEventFeed.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index d9a8ccd510..77ae5b653a 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge feed = new DailyChallengeEventFeed { RelativeSizeAxes = Axes.Both, + Height = 0.3f, Anchor = Anchor.Centre, Origin = Anchor.Centre, } @@ -44,13 +45,13 @@ namespace osu.Game.Tests.Visual.DailyChallenge if (feed.IsNotNull()) feed.Width = width; }); - AddSliderStep("adjust height", 0.1f, 1, 1, height => + AddSliderStep("adjust height", 0.1f, 1, 0.3f, height => { if (feed.IsNotNull()) feed.Height = height; }); - AddStep("add normal score", () => + AddRepeatStep("add normal score", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -60,9 +61,9 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, RNG.Next(1_000_000), null); feed.AddNewScore(ev); - }); + }, 50); - AddStep("add new user best", () => + AddRepeatStep("add new user best", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.DailyChallenge testScore.TotalScore = RNG.Next(1_000_000); feed.AddNewScore(ev); - }); + }, 50); - AddStep("add top 10 score", () => + AddRepeatStep("add top 10 score", () => { var ev = new NewScoreEvent(1, new APIUser { @@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, RNG.Next(1_000_000), RNG.Next(1, 10)); feed.AddNewScore(ev); - }); + }, 50); } } } From c90d345ff98ec5ba15ffcc1cc2c0a46cf193b352 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:20:17 +0900 Subject: [PATCH 331/670] Scroll content forever rather than aggressively fading --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index e76238abad..24e530223e 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -54,7 +54,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge PresentScore = PresentScore, }; flow.Add(row); - row.Delay(15000).Then().FadeOut(300, Easing.OutQuint).Expire(); } protected override void Update() @@ -65,6 +64,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { var row = flow[i]; + row.Alpha = Math.Max(0, (row.Y + flow.DrawHeight) / flow.DrawHeight); + if (row.Y < -flow.DrawHeight) { row.RemoveAndDisposeImmediately(); From f1dda4ab1ed5c040569c4224389abfe4b30290b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jul 2024 14:28:09 +0900 Subject: [PATCH 332/670] Fix too many event rows displaying after spending a long time in gameplay/results --- .../TestSceneDailyChallengeEventFeed.cs | 33 ++++++++++++++++--- .../DailyChallenge/DailyChallengeEventFeed.cs | 24 ++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs index 77ae5b653a..4b784f661d 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeEventFeed.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -17,14 +18,14 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallengeEventFeed : OsuTestScene { + private DailyChallengeEventFeed feed = null!; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Test] - public void TestBasicAppearance() + [SetUpSteps] + public void SetUpSteps() { - DailyChallengeEventFeed feed = null!; - AddStep("create content", () => Children = new Drawable[] { new Box @@ -40,6 +41,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge Origin = Anchor.Centre, } }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => { if (feed.IsNotNull()) @@ -50,7 +52,11 @@ namespace osu.Game.Tests.Visual.DailyChallenge if (feed.IsNotNull()) feed.Height = height; }); + } + [Test] + public void TestBasicAppearance() + { AddRepeatStep("add normal score", () => { var ev = new NewScoreEvent(1, new APIUser @@ -90,5 +96,24 @@ namespace osu.Game.Tests.Visual.DailyChallenge feed.AddNewScore(ev); }, 50); } + + [Test] + public void TestMassAdd() + { + AddStep("add 1000 scores at once", () => + { + for (int i = 0; i < 1000; i++) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + feed.AddNewScore(ev); + } + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 24e530223e..c23deec8ac 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -22,6 +22,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public Action? PresentScore { get; init; } + private readonly Queue newScores = new Queue(); + [BackgroundDependencyLoader] private void load() { @@ -47,19 +49,27 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public void AddNewScore(NewScoreEvent newScoreEvent) { - var row = new NewScoreEventRow(newScoreEvent) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - PresentScore = PresentScore, - }; - flow.Add(row); + newScores.Enqueue(newScoreEvent); + + // ensure things don't get too out-of-hand. + if (newScores.Count > 25) + newScores.Dequeue(); } protected override void Update() { base.Update(); + while (newScores.TryDequeue(out var newScore)) + { + flow.Add(new NewScoreEventRow(newScore) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + PresentScore = PresentScore, + }); + } + for (int i = 0; i < flow.Count; ++i) { var row = flow[i]; From e1ccf688019cd871e02aed858e07e0439243879d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 08:51:48 +0300 Subject: [PATCH 333/670] Revert "Add failing test case" This reverts commit 0182f3d7c3e52fd63f9267148498ee069d5644f0. --- .../DailyChallenge/TestSceneDailyChallenge.cs | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 0e0927a678..cd09a1d20f 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,12 +4,9 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Screens.Ranking; -using osu.Game.Screens.SelectV2.Leaderboards; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; @@ -17,14 +14,10 @@ namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { - [SetUpSteps] - public override void SetUpSteps() + [Test] + public void TestDailyChallenge() { - base.SetUpSteps(); - - Room room = null!; - - AddStep("add room", () => API.Perform(new CreateRoomRequest(room = new Room + var room = new Room { RoomID = { Value = 1234 }, Name = { Value = "Daily Challenge: June 4, 2024" }, @@ -38,22 +31,10 @@ namespace osu.Game.Tests.Visual.DailyChallenge }, EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, Category = { Value = RoomCategory.DailyChallenge } - }))); + }; + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } - - [Test] - public void TestDailyChallenge() - { - } - - [Test] - public void TestScoreNavigation() - { - AddStep("click on score", () => this.ChildrenOfType().First().TriggerClick()); - AddUntilStep("wait for load", () => Stack.CurrentScreen is ResultsScreen results && results.IsLoaded); - AddAssert("replay download button exists", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); - } } } From 9d5fbb8b4fa1739dd07b16eab84663a826eacc95 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 07:59:33 +0300 Subject: [PATCH 334/670] Fix target score selection abruptly discarded after opening results screen --- .../OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs index 831b6538a7..32be7f21b0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); - Schedule(() => SelectedScore.Value = scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); + Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); return scoreInfos; } From f3dd1facf15935fcbd93b1b5383e5a69997170b4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 08:38:20 +0300 Subject: [PATCH 335/670] Add failing test case --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index a52d29a120..7527647b9c 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); } } @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); waitForDisplay(); - AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() >= beforePanelCount + scores_per_result); + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); } } From 615f07d54cdb6db9ae7c58fddbb48c1ff3ed63ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 25 Jul 2024 09:09:53 +0300 Subject: [PATCH 336/670] Fix results screen fetching more scores twice --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db53..4fdfc2beb8 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -267,7 +267,8 @@ namespace osu.Game.Screens.Ranking foreach (var s in scores) addScore(s); - lastFetchCompleted = true; + // allow a frame for scroll container to adjust its dimensions with the added scores before fetching again. + Schedule(() => lastFetchCompleted = true); if (ScorePanelList.IsEmpty) { From 8d89557ab88b0bb0508626d91fd69276b0ef0eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 11:11:54 +0200 Subject: [PATCH 337/670] Fix not being able to send chat reports on daily challenge screen Something something some people cannot be trusted with a textbox. --- .../DailyChallenge/DailyChallenge.cs | 271 +++++++++--------- 1 file changed, 138 insertions(+), 133 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index aab0458275..235361dfaa 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Screens; @@ -126,169 +127,173 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge RelativeSizeAxes = Axes.Both, }, new Header(ButtonSystemStrings.DailyChallenge.ToSentence(), null), - new GridContainer + new PopoverContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Child = new GridContainer { - Horizontal = WaveOverlayContainer.WIDTH_PADDING, - Top = Header.HEIGHT, - }, - RowDimensions = - [ - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 30), - new Dimension(GridSizeMode.Absolute, 50) - ], - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - new DrawableRoomPlaylistItem(playlistItem) - { - RelativeSizeAxes = Axes.X, - AllowReordering = false, - Scale = new Vector2(1.4f), - Width = 1 / 1.4f, - } + Horizontal = WaveOverlayContainer.WIDTH_PADDING, + Top = Header.HEIGHT, }, - null, + RowDimensions = [ - new Container + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 30), + new Dimension(GridSizeMode.Absolute, 50) + ], + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Children = new Drawable[] + new DrawableRoomPlaylistItem(playlistItem) { - new Box + RelativeSizeAxes = Axes.X, + AllowReordering = false, + Scale = new Vector2(1.4f), + Width = 1 / 1.4f, + } + }, + null, + [ + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - ColumnDimensions = - [ - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension() - ], - Content = new[] + new Box { - new Drawable?[] + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + ColumnDimensions = + [ + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension() + ], + Content = new[] { - new GridContainer + new Drawable?[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RowDimensions = - [ - new Dimension(), - new Dimension() - ], - Content = new[] + new GridContainer { - new Drawable[] - { - new DailyChallengeCarousel - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new DailyChallengeTimeRemainingRing(), - breakdown = new DailyChallengeScoreBreakdown(), - } - } - }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RowDimensions = [ - feed = new DailyChallengeEventFeed - { - RelativeSizeAxes = Axes.Both, - PresentScore = presentScore - } + new Dimension(), + new Dimension() ], - }, - }, - null, - // Middle column (leaderboard) - leaderboard = new DailyChallengeLeaderboard(room, playlistItem) - { - RelativeSizeAxes = Axes.Both, - PresentScore = presentScore, - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] + Content = new[] { - new SectionHeader("Chat") + new Drawable[] + { + new DailyChallengeCarousel + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new DailyChallengeTimeRemainingRing(), + breakdown = new DailyChallengeScoreBreakdown(), + } + } + }, + [ + feed = new DailyChallengeEventFeed + { + RelativeSizeAxes = Axes.Both, + PresentScore = presentScore + } + ], }, - [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] }, - RowDimensions = - [ - new Dimension(GridSizeMode.AutoSize), - new Dimension() - ] - }, + null, + // Middle column (leaderboard) + leaderboard = new DailyChallengeLeaderboard(room, playlistItem) + { + RelativeSizeAxes = Axes.Both, + PresentScore = presentScore, + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new SectionHeader("Chat") + }, + [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }] + }, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension() + ] + }, + } } } } } - } - ], - null, - [ - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + ], + null, + [ + new Container { - Horizontal = -WaveOverlayContainer.WIDTH_PADDING, - }, - Children = new Drawable[] - { - new Box + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, + Horizontal = -WaveOverlayContainer.WIDTH_PADDING, }, - footerButtons = new FillFlowContainer + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding(5), - Spacing = new Vector2(10), - Children = new Drawable[] + new Box { - new PlaylistsReadyButton + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + footerButtons = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(5), + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(250, 1), - Action = startPlay + new PlaylistsReadyButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(250, 1), + Action = startPlay + } } - } - }, + }, + } } - } - ], + ], + } } } } From 8dbd4d70ff36734c1af64471eed86932dfb55ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 11:42:56 +0200 Subject: [PATCH 338/670] Fix crash when toggling extended statistics visibility during results load Closes https://github.com/ppy/osu/issues/29066. Initially I fixed this at where the assert is right now: https://github.com/ppy/osu/blob/9790c5a574b782c41c8c6da99ad8c42dfadc9de8/osu.Game/Screens/Ranking/ResultsScreen.cs#L333 but because of the weird way that visible state management is done in this screen that made it possible for the extended statistics to be visible *behind* the score panels, without the score panels making way for it. So this is in a way safer, because it prevents the visibility state of the extended statistics from changing in the first place if there is no score selected (yet). This can be also seen in playlists, at least. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44b270db53..283a74e35f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -398,7 +398,8 @@ namespace osu.Game.Screens.Ranking break; case GlobalAction.Select: - StatisticsPanel.ToggleVisibility(); + if (SelectedScore.Value != null) + StatisticsPanel.ToggleVisibility(); return true; } From e564b1dc9e470cc77af1ba38eacb8b3c08966409 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 25 Jul 2024 18:23:01 +0800 Subject: [PATCH 339/670] Fix cursor trail alignment issue with UI panels - Convert cursor trail coordinates to local space before storing. - Apply necessary transformations to align with other UI elements. - Ensure cursor trail remains connected during UI panel movements. --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 30a77db5a1..49e4ee18c1 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void addPart(Vector2 screenSpacePosition) { - parts[currentIndex].Position = screenSpacePosition; + parts[currentIndex].Position = ToLocalSpace(screenSpacePosition); parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; @@ -285,9 +285,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (time - part.Time >= 1) continue; + Vector2 screenSpacePos = Source.ToScreenSpace(part.Position); + vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -296,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -305,7 +307,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + size.X * (1 - originPosition.X), part.Position.Y - size.Y * originPosition.Y), + Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -314,7 +316,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - size.X * originPosition.X, part.Position.Y - size.Y * originPosition.Y), + Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, From 3bb30d7ff93edbf655e67dd6c25edb0516d300c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 25 Jul 2024 13:06:18 +0200 Subject: [PATCH 340/670] Fix several missing properties on `MultiplayerScore` You wouldn't think this would be an actual thing that can happen to us, but it is. The most important one by far is `MaximumStatistics`; that is the root cause behind why stuff like spinner ticks or slider tails wasn't showing. On a better day we should probably do cleanup to unify these models better, but today is not that day. --- osu.Game/Online/Rooms/MultiplayerScore.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index f1b9584d57..faa66c571d 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -46,6 +46,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("statistics")] public Dictionary Statistics = new Dictionary(); + [JsonProperty("maximum_statistics")] + public Dictionary MaximumStatistics = new Dictionary(); + [JsonProperty("passed")] public bool Passed { get; set; } @@ -58,9 +61,15 @@ namespace osu.Game.Online.Rooms [JsonProperty("position")] public int? Position { get; set; } + [JsonProperty("pp")] + public double? PP { get; set; } + [JsonProperty("has_replay")] public bool HasReplay { get; set; } + [JsonProperty("ranked")] + public bool Ranked { get; set; } + /// /// Any scores in the room around this score. /// @@ -83,13 +92,17 @@ namespace osu.Game.Online.Rooms MaxCombo = MaxCombo, BeatmapInfo = beatmap, Ruleset = rulesets.GetRuleset(playlistItem.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {playlistItem.RulesetID} not found locally"), + Passed = Passed, Statistics = Statistics, + MaximumStatistics = MaximumStatistics, User = User, Accuracy = Accuracy, Date = EndedAt, HasOnlineReplay = HasReplay, Rank = Rank, Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty(), + PP = PP, + Ranked = Ranked, Position = Position, }; From 3e8917cadb46c051bc2b399fababf491cd08c298 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Jul 2024 05:08:13 +0300 Subject: [PATCH 341/670] Add test case against resetting score in download button --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5b32f380b9..061e8ea7e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -117,6 +117,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("state entered downloading", () => downloadStarted); AddUntilStep("state left downloading", () => downloadFinished); + + AddStep("change score to null", () => downloadButton.Score.Value = null); + AddUntilStep("state changed to unknown", () => downloadButton.State.Value, () => Is.EqualTo(DownloadState.Unknown)); } [Test] From c558dfdf138523482dfed8640d5f276e648af970 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 26 Jul 2024 05:11:54 +0300 Subject: [PATCH 342/670] Reset download state when score is changed --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index aac29ad269..5e2161c251 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -88,6 +88,8 @@ namespace osu.Game.Screens.Ranking State.ValueChanged -= exportWhenReady; downloadTracker?.RemoveAndDisposeImmediately(); + downloadTracker = null; + State.SetDefault(); if (score.NewValue != null) { From c17cabd98136f94ffd3b3854cc9c85d191b47ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 07:44:02 +0200 Subject: [PATCH 343/670] Adjust alpha for rows for better visibility --- .../OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index c23deec8ac..160ad83c8a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -74,7 +75,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { var row = flow[i]; - row.Alpha = Math.Max(0, (row.Y + flow.DrawHeight) / flow.DrawHeight); + row.Alpha = Interpolation.ValueAt(Math.Clamp(row.Y + flow.DrawHeight, 0, flow.DrawHeight), 0f, 1f, 0, flow.DrawHeight, Easing.Out); if (row.Y < -flow.DrawHeight) { From 662e9eab8c77bd49be3d557d437ac497464a8d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 08:07:27 +0200 Subject: [PATCH 344/670] Don't force exit to main menu when presenting scores from within online screens Struck me as weird when reviewing https://github.com/ppy/osu/pull/29057. Like sure, that PR adds the replay button, but it's a bit terrible that clicking the button quits the daily challenge screen and you're back at main menu when done watching...? Also extended to cover playlists and multiplayer, which have the same issue. --- osu.Game/OsuGame.cs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 388a98d947..2195576be1 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -63,6 +63,8 @@ using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -749,23 +751,34 @@ namespace osu.Game return; } - // This should be able to be performed from song select, but that is disabled for now + // This should be able to be performed from song select always, but that is disabled for now // due to the weird decoupled ruleset logic (which can cause a crash in certain filter scenarios). // // As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select. // This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the // song select leaderboard). + // Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match + // (playlists / multiplayer / daily challenge). IEnumerable validScreens = Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset) - ? new[] { typeof(SongSelect) } + ? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) } : Array.Empty(); PerformFromScreen(screen => { Logger.Log($"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset}) to match score"); - Ruleset.Value = databasedScore.ScoreInfo.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + // some screens (mostly online) disable the ruleset/beatmap bindable. + // attempting to set the ruleset/beatmap in that state will crash. + // however, the `validScreens` pre-check above should ensure that we actually never come from one of those screens + // while simultaneously having mismatched ruleset/beatmap. + // therefore this is just a safety against touching the possibly-disabled bindables if we don't actually have to touch them. + // if it ever fails, then this probably *should* crash anyhow (so that we can fix it). + if (!Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset)) + Ruleset.Value = databasedScore.ScoreInfo.Ruleset; + + if (!Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap)) + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); switch (presentType) { From 174dc91f4ba0f34f9bfe5772157896eb75affb2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 09:49:36 +0200 Subject: [PATCH 345/670] Implement component for displaying running totals in daily challenge Total pass count and cumulative total score, to be more precise. --- .../TestSceneDailyChallengeTotalsDisplay.cs | 86 ++++++++++++ .../DailyChallengeTotalsDisplay.cs | 126 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs new file mode 100644 index 0000000000..ba5a0989d4 --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.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.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeTotalsDisplay : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + + [Test] + public void TestBasicAppearance() + { + DailyChallengeTotalsDisplay totals = null!; + + AddStep("create content", () => Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + totals = new DailyChallengeTotalsDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + AddSliderStep("adjust width", 0.1f, 1, 1, width => + { + if (totals.IsNotNull()) + totals.Width = width; + }); + AddSliderStep("adjust height", 0.1f, 1, 1, height => + { + if (totals.IsNotNull()) + totals.Height = height; + }); + + AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000)); + + AddStep("add normal score", () => + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + totals.AddNewScore(ev); + }); + + AddStep("spam scores", () => + { + for (int i = 0; i < 1000; ++i) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), RNG.Next(11, 1000)); + + var testScore = TestResources.CreateTestScoreInfo(); + testScore.TotalScore = RNG.Next(1_000_000); + + totals.AddNewScore(ev); + } + }); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs new file mode 100644 index 0000000000..464022639f --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -0,0 +1,126 @@ +// 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.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeTotalsDisplay : CompositeDrawable + { + private Container passCountContainer = null!; + private TotalRollingCounter passCounter = null!; + private Container totalScoreContainer = null!; + private TotalRollingCounter totalScoreCounter = null!; + + private long totalPassCountInstantaneous; + private long cumulativeTotalScoreInstantaneous; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = + [ + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + ], + Content = new[] + { + new Drawable[] + { + new SectionHeader("Total pass count") + }, + new Drawable[] + { + passCountContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = passCounter = new TotalRollingCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + new Drawable[] + { + new SectionHeader("Cumulative total score") + }, + new Drawable[] + { + totalScoreContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = totalScoreCounter = new TotalRollingCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + } + }; + } + + public void SetInitialCounts(long totalPassCount, long cumulativeTotalScore) + { + totalPassCountInstantaneous = totalPassCount; + cumulativeTotalScoreInstantaneous = cumulativeTotalScore; + } + + public void AddNewScore(NewScoreEvent ev) + { + totalPassCountInstantaneous += 1; + cumulativeTotalScoreInstantaneous += ev.TotalScore; + } + + protected override void Update() + { + base.Update(); + + passCounter.Current.Value = totalPassCountInstantaneous; + totalScoreCounter.Current.Value = cumulativeTotalScoreInstantaneous; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + var totalPassCountProportionOfParent = Vector2.Divide(passCountContainer.DrawSize, passCounter.DrawSize); + passCounter.Scale = new Vector2(Math.Min(Math.Min(totalPassCountProportionOfParent.X, totalPassCountProportionOfParent.Y) * 0.8f, 1)); + + var totalScoreTextProportionOfParent = Vector2.Divide(totalScoreContainer.DrawSize, totalScoreCounter.DrawSize); + totalScoreCounter.Scale = new Vector2(Math.Min(Math.Min(totalScoreTextProportionOfParent.X, totalScoreTextProportionOfParent.Y) * 0.8f, 1)); + } + + private partial class TotalRollingCounter : RollingCounter + { + protected override double RollingDuration => 400; + + protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText + { + Font = OsuFont.Default.With(size: 80f, fixedWidth: true), + }; + + protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); + } + } +} From a8851950bccaf8abbe11f05ce1329ebde256ceb6 Mon Sep 17 00:00:00 2001 From: Cameron Brown Date: Fri, 26 Jul 2024 18:10:11 +1000 Subject: [PATCH 346/670] Update the beatmap of Daily Challenge's mods overlay when beatmap is set - #29094 --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa..057bbd6be4 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -379,6 +379,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); + + userModsSelectOverlay.Beatmap.Value = Beatmap.Value; + applyLoopingToTrack(); } From 17f00ec0a6e87e184ae895df4ec05f85c11f6cec Mon Sep 17 00:00:00 2001 From: Cameron Brown Date: Fri, 26 Jul 2024 18:29:50 +1000 Subject: [PATCH 347/670] Bind the mod select overlay's Beatmap to OsuScreen.Beatmap in constructor Suggested by @bdach! --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 057bbd6be4..a4b251bf5b 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -301,6 +301,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge LoadComponent(userModsSelectOverlay = new RoomModSelectOverlay { + Beatmap = { BindTarget = Beatmap }, SelectedMods = { BindTarget = userMods }, IsValidMod = _ => false }); @@ -380,8 +381,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); - userModsSelectOverlay.Beatmap.Value = Beatmap.Value; - applyLoopingToTrack(); } From f9cfc7d96cfbf42a9e40172b55c14ef92a2e9827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 10:53:12 +0200 Subject: [PATCH 348/670] Fix preview tracks not stopping playback when suspending/exiting daily challenge screen Closes https://github.com/ppy/osu/issues/29083. --- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa..6ff0eb2452 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; @@ -40,7 +41,8 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { - public partial class DailyChallenge : OsuScreen + [Cached(typeof(IPreviewTrackOwner))] + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -91,6 +93,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] protected IAPIProvider API { get; private set; } = null!; + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + public override bool DisallowExternalBeatmapRulesetChanges => true; public DailyChallenge(Room room) @@ -441,6 +446,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge userModsSelectOverlay.Hide(); cancelTrackLooping(); + previewTrackManager.StopAnyPlaying(this); } public override bool OnExiting(ScreenExitEvent e) @@ -448,6 +454,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge waves.Hide(); userModsSelectOverlay.Hide(); cancelTrackLooping(); + previewTrackManager.StopAnyPlaying(this); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); roomManager.PartRoom(); From 1abcf16231eebe0d24dd0b7dab1a9ebea86ad7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 11:50:43 +0200 Subject: [PATCH 349/670] Fix daily challenge screen not applying track adjustments from mods Closes https://github.com/ppy/osu/issues/29093. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index a4b251bf5b..56398090d0 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -93,6 +93,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool? ApplyModTrackAdjustments => true; + public DailyChallenge(Room room) { this.room = room; From 1ad0b31217d8df8e29ed02165aa83bd3a665a788 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 19:04:04 +0900 Subject: [PATCH 350/670] Add required pieces to `MultiplayerPlaylistItemStats` for total score tracking --- osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs index d13705bf5b..6e50242556 100644 --- a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs +++ b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs @@ -25,5 +25,14 @@ namespace osu.Game.Online.Metadata /// [Key(1)] public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS]; + + /// + /// The cumulative total of all passing scores (across all users) in the playlist so far. + /// + [Key(2)] + public long TotalPlaylistScore { get; set; } + + [Key(3)] + public ulong LastProcessedScoreID { get; set; } } } From 2e37f3b5de2be1e94bc3a29f4f608f021aeadb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 12:34:23 +0200 Subject: [PATCH 351/670] Hook up score totals display to daily challenge screen --- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 235361dfaa..17241a5fd6 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -59,6 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private IDisposable? userModsSelectOverlayRegistration; private DailyChallengeScoreBreakdown breakdown = null!; + private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; [Cached] @@ -211,6 +212,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { new DailyChallengeTimeRemainingRing(), breakdown = new DailyChallengeScoreBreakdown(), + totals = new DailyChallengeTotalsDisplay(), } } }, @@ -351,6 +353,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Schedule(() => { breakdown.AddNewScore(ev); + totals.AddNewScore(ev); feed.AddNewScore(ev); if (e.NewRank <= 50) @@ -421,7 +424,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID); if (itemStats == null) return; - Schedule(() => breakdown.SetInitialCounts(itemStats.TotalScoreDistribution)); + Schedule(() => + { + breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); + totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.TotalPlaylistScore); + }); }); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; From 19affa7062bfd7f82e1a33a61e6427a74a1f2463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 20:42:01 +0900 Subject: [PATCH 352/670] Rename new property to match true usage (per item) Also document a bit more. --- osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs | 7 +++++-- .../Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs index 6e50242556..19a2bde497 100644 --- a/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs +++ b/osu.Game/Online/Metadata/MultiplayerPlaylistItemStats.cs @@ -27,11 +27,14 @@ namespace osu.Game.Online.Metadata public long[] TotalScoreDistribution { get; set; } = new long[TOTAL_SCORE_DISTRIBUTION_BINS]; /// - /// The cumulative total of all passing scores (across all users) in the playlist so far. + /// The cumulative total of all passing scores (across all users) for the playlist item so far. /// [Key(2)] - public long TotalPlaylistScore { get; set; } + public long CumulativeScore { get; set; } + /// + /// The last score to have been processed into provided statistics. Generally only for server-side accounting purposes. + /// [Key(3)] public ulong LastProcessedScoreID { get; set; } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 17241a5fd6..ff37d7c970 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -427,7 +427,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Schedule(() => { breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); - totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.TotalPlaylistScore); + totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore); }); }); From 2caaebb6705bf9df5f0fd8572c1a013230cf6ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 13:47:41 +0200 Subject: [PATCH 353/670] Add tooltip with counts to daily challenge score breakdown chart --- .../DailyChallengeScoreBreakdown.cs | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 0c7202f7cf..45bda9f185 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -6,6 +6,7 @@ using System.Linq; 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.Framework.Localisation; using osu.Game.Graphics; @@ -44,23 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge for (int i = 0; i < bin_count; ++i) { - LocalisableString? label = null; - - switch (i) - { - case 2: - case 4: - case 6: - case 8: - label = @$"{100 * i}k"; - break; - - case 10: - label = @"1M"; - break; - } - - barsContainer.Add(new Bar(label) + barsContainer.Add(new Bar(100_000 * i, 100_000 * (i + 1) - 1) { Width = 1f / bin_count, }); @@ -113,18 +98,20 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge barsContainer[i].UpdateCounts(bins[i], max); } - private partial class Bar : CompositeDrawable + private partial class Bar : CompositeDrawable, IHasTooltip { - private readonly LocalisableString? label; + private readonly int binStart; + private readonly int binEnd; private long count; private long max; public Container CircularBar { get; private set; } = null!; - public Bar(LocalisableString? label = null) + public Bar(int binStart, int binEnd) { - this.label = label; + this.binStart = binStart; + this.binEnd = binEnd; } [BackgroundDependencyLoader] @@ -159,13 +146,29 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } }); + string? label = null; + + switch (binStart) + { + case 200_000: + case 400_000: + case 600_000: + case 800_000: + label = @$"{binStart / 1000}k"; + break; + + case 1_000_000: + label = @"1M"; + break; + } + if (label != null) { AddInternal(new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomCentre, - Text = label.Value, + Text = label, Colour = colourProvider.Content2, }); } @@ -189,6 +192,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (isIncrement) CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint); } + + public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, binStart, binEnd); } } } From fc0ade2c61405b80b3c15acc042d678fa545d720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 14:31:21 +0200 Subject: [PATCH 354/670] Highlight where local user's best is on the breakdown --- .../TestSceneDailyChallengeScoreBreakdown.cs | 3 + .../DailyChallenge/DailyChallenge.cs | 2 + .../DailyChallengeLeaderboard.cs | 7 +- .../DailyChallengeScoreBreakdown.cs | 101 ++++++++++++++---- 4 files changed, 92 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 81ec95d8d2..631aafb58f 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; @@ -61,6 +62,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge breakdown.AddNewScore(ev); }); + AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) }); + AddStep("unset user score", () => breakdown.UserBestScore.Value = null); } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index a4b251bf5b..44c47e18d6 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -324,6 +324,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; + + ((IBindable)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore); } private void presentScore(long id) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 4c4622bba3..d87a34405d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -22,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeLeaderboard : CompositeDrawable { + public IBindable UserBestScore => userBestScore; + private Bindable userBestScore = new Bindable(); + public Action? PresentScore { get; init; } private readonly Room room; @@ -130,7 +133,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge request.Success += req => Schedule(() => { var best = req.Scores.Select(s => s.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo)).ToArray(); - var userBest = req.UserScore?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); + + userBestScore.Value = req.UserScore; + var userBest = userBestScore.Value?.CreateScoreInfo(scoreManager, rulesets, playlistItem, beatmap.Value.BeatmapInfo); cancellationTokenSource?.Cancel(); cancellationTokenSource = null; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 45bda9f185..fce4f0452b 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -13,6 +14,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.DailyChallenge.Events; using osuTK; @@ -21,6 +23,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeScoreBreakdown : CompositeDrawable { + public Bindable UserBestScore { get; } = new Bindable(); + private FillFlowContainer barsContainer = null!; private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; @@ -52,6 +56,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } + protected override void LoadComplete() + { + base.LoadComplete(); + + UserBestScore.BindValueChanged(_ => + { + foreach (var bar in barsContainer) + bar.ContainsLocalUser.Value = UserBestScore.Value is not null && bar.BinStart <= UserBestScore.Value.TotalScore && UserBestScore.Value.TotalScore <= bar.BinEnd; + }); + } + public void AddNewScore(NewScoreEvent newScoreEvent) { int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); @@ -100,20 +115,32 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private partial class Bar : CompositeDrawable, IHasTooltip { - private readonly int binStart; - private readonly int binEnd; + public BindableBool ContainsLocalUser { get; } = new BindableBool(); + + public readonly int BinStart; + public readonly int BinEnd; private long count; private long max; public Container CircularBar { get; private set; } = null!; + private Box fill = null!; + private Box flashLayer = null!; + private OsuSpriteText userIndicator = null!; + public Bar(int binStart, int binEnd) { - this.binStart = binStart; - this.binEnd = binEnd; + this.BinStart = binStart; + this.BinEnd = binEnd; } + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -129,32 +156,52 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Masking = true, - Child = CircularBar = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.01f, - Masking = true, - CornerRadius = 10, - Colour = colourProvider.Highlight1, - Child = new Box + CircularBar = new Container { RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.01f, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + fill = new Box + { + RelativeSizeAxes = Axes.Both, + }, + flashLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + } + }, + userIndicator = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = colours.Orange1, + Text = "You", + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Alpha = 0, + RelativePositionAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 5, }, } - } + }, }); string? label = null; - switch (binStart) + switch (BinStart) { case 200_000: case 400_000: case 600_000: case 800_000: - label = @$"{binStart / 1000}k"; + label = @$"{BinStart / 1000}k"; break; case 1_000_000: @@ -174,6 +221,18 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } + protected override void LoadComplete() + { + base.LoadComplete(); + + ContainsLocalUser.BindValueChanged(_ => + { + fill.FadeColour(ContainsLocalUser.Value ? colours.Orange1 : colourProvider.Highlight1, 300, Easing.OutQuint); + userIndicator.FadeTo(ContainsLocalUser.Value ? 1 : 0, 300, Easing.OutQuint); + }, true); + FinishTransforms(true); + } + protected override void Update() { base.Update(); @@ -188,12 +247,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge count = newCount; max = newMax; - CircularBar.ResizeHeightTo(0.01f + 0.99f * count / max, 300, Easing.OutQuint); + float height = 0.01f + 0.99f * count / max; + CircularBar.ResizeHeightTo(height, 300, Easing.OutQuint); + userIndicator.MoveToY(-height, 300, Easing.OutQuint); if (isIncrement) - CircularBar.FlashColour(Colour4.White, 600, Easing.OutQuint); + flashLayer.FadeOutFromOne(600, Easing.OutQuint); } - public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, binStart, binEnd); + public LocalisableString TooltipText => LocalisableString.Format("{0:N0} passes in {1:N0} - {2:N0} range", count, BinStart, BinEnd); } } } From a870722ea691297c216283833215cf8316cb2f3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 21:43:23 +0900 Subject: [PATCH 355/670] Adjust easings and reduce character spacing slightly --- .../DailyChallengeTotalsDisplay.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs index 464022639f..cf8a60d4a2 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -113,11 +113,26 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private partial class TotalRollingCounter : RollingCounter { - protected override double RollingDuration => 400; + protected override double RollingDuration => 1000; + + protected override Easing RollingEasing => Easing.OutPow10; + + protected override bool IsRollingProportional => true; + + protected override double GetProportionalDuration(long currentValue, long newValue) + { + long change = Math.Abs(newValue - currentValue); + + if (change < 10) + return 0; + + return Math.Min(6000, RollingDuration * Math.Sqrt(change) / 100); + } protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Default.With(size: 80f, fixedWidth: true), + Spacing = new Vector2(-2, 0) }; protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); From 0996f9b0b51dbc7d2308c812385e603b6a05036d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jul 2024 14:45:39 +0200 Subject: [PATCH 356/670] Fix code quality --- .../OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs | 2 +- .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index d87a34405d..5efb656cea 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public partial class DailyChallengeLeaderboard : CompositeDrawable { public IBindable UserBestScore => userBestScore; - private Bindable userBestScore = new Bindable(); + private readonly Bindable userBestScore = new Bindable(); public Action? PresentScore { get; init; } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index fce4f0452b..cfec170cf6 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -131,8 +131,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public Bar(int binStart, int binEnd) { - this.BinStart = binStart; - this.BinEnd = binEnd; + BinStart = binStart; + BinEnd = binEnd; } [Resolved] From 96049807c4392ee68d85134418865cdb9946c296 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 23:20:32 +0900 Subject: [PATCH 357/670] Adjust weight and text in event feed output Just some minor adjustments. --- .../DailyChallenge/DailyChallengeEventFeed.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs index 160ad83c8a..044c599ae9 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeEventFeed.cs @@ -121,7 +121,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }, text = new LinkFlowContainer(t => { - t.Font = OsuFont.Default.With(weight: newScore.NewRank == null ? FontWeight.Medium : FontWeight.Bold); + FontWeight fontWeight = FontWeight.Medium; + + if (newScore.NewRank < 100) + fontWeight = FontWeight.Bold; + else if (newScore.NewRank < 1000) + fontWeight = FontWeight.SemiBold; + + t.Font = OsuFont.Default.With(weight: fontWeight); t.Colour = newScore.NewRank < 10 ? colours.Orange1 : Colour4.White; }) { @@ -132,8 +139,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }; text.AddUserLink(newScore.User); - text.AddText(" got "); - text.AddLink($"{newScore.TotalScore:N0} points", () => PresentScore?.Invoke(newScore.ScoreID)); + text.AddText(" scored "); + text.AddLink($"{newScore.TotalScore:N0}", () => PresentScore?.Invoke(newScore.ScoreID)); if (newScore.NewRank != null) text.AddText($" and achieved rank #{newScore.NewRank.Value:N0}"); From 0421e1e9d0bf16e6a947cc122694fff19f4d42c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Jul 2024 23:21:44 +0900 Subject: [PATCH 358/670] Reduce number spacing a bit more --- .../OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs index cf8a60d4a2..e2535ed806 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeTotalsDisplay.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge protected override OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Default.With(size: 80f, fixedWidth: true), - Spacing = new Vector2(-2, 0) + Spacing = new Vector2(-4, 0) }; protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(@"N0"); From 0cc6818b21f8bb88746269570e22e9ce399ab74a Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:44:50 +0800 Subject: [PATCH 359/670] allow hover to expand `ModCustomisationPanel` --- .../Overlays/Mods/ModCustomisationHeader.cs | 13 ++++++++++ .../Overlays/Mods/ModCustomisationPanel.cs | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index bf10e13515..fbdce7be6d 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,6 +29,8 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Expanded = new BindableBool(); + protected new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + public ModCustomisationHeader() { Action = Expanded.Toggle; @@ -91,5 +94,15 @@ namespace osu.Game.Overlays.Mods icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); } + + protected override bool OnHover(HoverEvent e) + { + if (Enabled.Value) + { + Parent.UpdateHoverExpansion(true); + } + + return base.OnHover(e); + } } } diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a1e64e8c49..a82e279d01 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -175,6 +175,22 @@ namespace osu.Game.Overlays.Mods content.ResizeHeightTo(header_height, 400, Easing.OutQuint); content.FadeOut(400, Easing.OutSine); } + + expandedByHovering = false; + } + + private bool expandedByHovering = false; + public void UpdateHoverExpansion(bool hovered) + { + if (hovered && !Expanded.Value) + { + Expanded.Value = true; + expandedByHovering = true; + } + else if (!hovered && expandedByHovering) + { + Expanded.Value = false; + } } private void updateMods() @@ -206,6 +222,14 @@ namespace osu.Game.Overlays.Mods public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; + + public new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + + protected override void OnHoverLost(HoverLostEvent e) + { + Parent.UpdateHoverExpansion(false); + base.OnHoverLost(e); + } } } } From a3576a55c229f16e3d4e251d566a8b2443fb0ebb Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:45:12 +0800 Subject: [PATCH 360/670] add test for hovering `ModCustomisationPanel` --- .../TestSceneModCustomisationPanel.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 9c0d185892..64ef7891c8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { @@ -19,6 +21,8 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); private ModCustomisationPanel panel = null!; + private ModCustomisationHeader header = null!; + private Container content = null!; [SetUp] public void SetUp() => Schedule(() => @@ -36,6 +40,9 @@ namespace osu.Game.Tests.Visual.UserInterface SelectedMods = { BindTarget = SelectedMods }, } }; + + header = panel.Children.OfType().First(); + content = panel.Children.OfType().First(); }); [Test] @@ -62,5 +69,68 @@ namespace osu.Game.Tests.Visual.UserInterface panel.Enabled.Value = panel.Expanded.Value = false; }); } + + [Test] + public void TestHoverExpand() + { + // Can not expand by hovering when no supported mod + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddAssert("neither expanded", () => !panel.Expanded.Value); + + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + } + + AddStep("add customisable mod", () => + { + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); + + // Can expand by hovering when supported mod + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("expanded", () => panel.Expanded.Value); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddAssert("still expanded", () => panel.Expanded.Value); + } + + // Will collapse when mouse left from content + { + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + } + + // Will collapse when mouse left from header + { + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + + AddAssert("expanded", () => panel.Expanded.Value); + + AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("not expanded", () => !panel.Expanded.Value); + } + + // Not collapse when mouse left if not expanded by hovering + { + AddStep("expand not by hovering", () => panel.Expanded.Value = true); + + AddStep("hover content", () => InputManager.MoveMouseTo(content)); + + AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); + + AddAssert("still expanded", () => panel.Expanded.Value); + } + } } } From aed81d97584353d06431fb7767690fa22ecc3f19 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 22:56:07 +0800 Subject: [PATCH 361/670] make code inspector happy --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 4 ++-- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index fbdce7be6d..5a9e6099e6 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Expanded = new BindableBool(); - protected new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + protected new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; public ModCustomisationHeader() { @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Mods { if (Enabled.Value) { - Parent.UpdateHoverExpansion(true); + Parent?.UpdateHoverExpansion(true); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a82e279d01..ee8232b79a 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -179,7 +179,8 @@ namespace osu.Game.Overlays.Mods expandedByHovering = false; } - private bool expandedByHovering = false; + private bool expandedByHovering; + public void UpdateHoverExpansion(bool hovered) { if (hovered && !Expanded.Value) @@ -223,11 +224,11 @@ namespace osu.Game.Overlays.Mods public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; - public new ModCustomisationPanel Parent => (ModCustomisationPanel)base.Parent; + public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; protected override void OnHoverLost(HoverLostEvent e) { - Parent.UpdateHoverExpansion(false); + Parent?.UpdateHoverExpansion(false); base.OnHoverLost(e); } } From bd017aea38f48746bb5148d7315e33dd9463f2ee Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 26 Jul 2024 23:58:52 +0800 Subject: [PATCH 362/670] fix `TestPreexistingSelection` failing --- osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index e4622ffcf9..77909d6936 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0)); AddStep("reset mods", () => SelectedMods.SetDefault()); AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true)); + AddStep("reset mouse", () => InputManager.MoveMouseTo(Vector2.One)); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo)); AddStep("set up presets", () => { From 9323f89357f0fd070f3aa12e7559d8ee9572d925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 27 Jul 2024 02:06:56 +0900 Subject: [PATCH 363/670] Fix "Beatmap not downloaded" tooltip hint not showing in daily challenge --- osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs | 4 ++-- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs index 813e243449..2e669fd1b2 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ReadyButton.cs @@ -21,8 +21,8 @@ namespace osu.Game.Screens.OnlinePlay.Components private void load(OnlinePlayBeatmapAvailabilityTracker beatmapTracker) { availability.BindTo(beatmapTracker.Availability); - availability.BindValueChanged(_ => updateState()); + Enabled.BindValueChanged(_ => updateState(), true); } @@ -33,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { get { - if (Enabled.Value) + if (base.Enabled.Value) return string.Empty; if (availability.Value.State != DownloadState.LocallyAvailable) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 91a3edbea3..4b00678b01 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -68,9 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { get { - if (Enabled.Value) - return string.Empty; - if (!enoughTimeLeft) return "No time left!"; From d55e861b906f8b049f0af5248ad78e9a36b99dd7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 26 Jul 2024 16:55:15 -0700 Subject: [PATCH 364/670] Fix daily challenge background clipping when settings/notifications is opened --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8538cbbb59..c8e1434e37 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -106,6 +106,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge this.room = room; playlistItem = room.Playlist.Single(); roomManager = new RoomManager(); + Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From b97d96fcb0189b736984ec1ecb0fdebc0c55d07d Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 15:15:14 +0800 Subject: [PATCH 365/670] Fix panel collapse when hovering dropdown menu --- .../Overlays/Mods/ModCustomisationPanel.cs | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index ee8232b79a..d906e704e0 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -81,6 +83,7 @@ namespace osu.Game.Overlays.Mods Colour = Color4.Black.Opacity(0.25f), }, Expanded = { BindTarget = Expanded }, + ExpandedByHovering = { BindTarget = ExpandedByHovering }, Children = new Drawable[] { new Box @@ -176,20 +179,22 @@ namespace osu.Game.Overlays.Mods content.FadeOut(400, Easing.OutSine); } - expandedByHovering = false; + ExpandedByHovering.Value = false; } - private bool expandedByHovering; + public readonly BindableBool ExpandedByHovering = new BindableBool(); public void UpdateHoverExpansion(bool hovered) { if (hovered && !Expanded.Value) { Expanded.Value = true; - expandedByHovering = true; + ExpandedByHovering.Value = true; } - else if (!hovered && expandedByHovering) + else if (!hovered && ExpandedByHovering.Value) { + Debug.Assert(Expanded.Value); + Expanded.Value = false; } } @@ -220,17 +225,35 @@ namespace osu.Game.Overlays.Mods private partial class FocusGrabbingContainer : InputBlockingContainer { public IBindable Expanded { get; } = new BindableBool(); + public IBindable ExpandedByHovering { get; } = new BindableBool(); public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private InputManager inputManager = null!; + + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + } + protected override void OnHoverLost(HoverLostEvent e) { - Parent?.UpdateHoverExpansion(false); + if (ExpandedByHovering.Value && !hasHoveredchild()) + Parent?.UpdateHoverExpansion(false); + base.OnHoverLost(e); } + + private bool hasHoveredchild() + { + return inputManager.HoveredDrawables.Any(parentIsThis); + + bool parentIsThis(Drawable d) + => d is not null && (d == this || parentIsThis(d.Parent)); + } } } } From fc842868a98132c2cedb5952b26cb55feb9b2a7e Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 16:24:20 +0800 Subject: [PATCH 366/670] fix code quality --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index d906e704e0..f17d2f39e6 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -236,7 +236,8 @@ namespace osu.Game.Overlays.Mods protected override void LoadComplete() { - inputManager = GetContainingInputManager(); + base.LoadComplete(); + inputManager = GetContainingInputManager()!; } protected override void OnHoverLost(HoverLostEvent e) @@ -251,7 +252,7 @@ namespace osu.Game.Overlays.Mods { return inputManager.HoveredDrawables.Any(parentIsThis); - bool parentIsThis(Drawable d) + bool parentIsThis(Drawable? d) => d is not null && (d == this || parentIsThis(d.Parent)); } } From c2711d0c4e3b5e33982f31d1344075a62fc5814a Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 17:25:44 +0800 Subject: [PATCH 367/670] Implement chatline background altering --- osu.Game/Overlays/Chat/ChatLine.cs | 23 +++++++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 8 ++++++++ 2 files changed, 31 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 9bcca3ac9d..922d040d54 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -69,6 +69,29 @@ namespace osu.Game.Overlays.Chat private Container? highlight; + private Drawable? background; + + private bool alteringBackground; + + public bool AlteringBackground + { + get => alteringBackground; + set + { + alteringBackground = value; + + if (background == null) + AddInternal(background = new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + + background.Alpha = value ? 0.04f : 0f; + } + } + /// /// The colour used to paint the author's username. /// diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index aa17df4907..c817417a44 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,6 +104,13 @@ namespace osu.Game.Overlays.Chat highlightedMessage.Value = null; }); + private void processMessageBackgroundAltering() + { + for (int i = 0; i < ChatLineFlow.Count(); i++) + if (ChatLineFlow[i] is ChatLine chatline) + chatline.AlteringBackground = i % 2 == 0; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -158,6 +165,7 @@ namespace osu.Game.Overlays.Chat scroll.ScrollToEnd(); processMessageHighlighting(); + processMessageBackgroundAltering(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 77d64e0c3d593d4b912a6fc8d2f1e16a9e46e9b8 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Sat, 27 Jul 2024 17:59:38 +0800 Subject: [PATCH 368/670] replace with `ReceivePositionalInputAt` --- .../Overlays/Mods/ModCustomisationPanel.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index f17d2f39e6..9795b61762 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -232,29 +231,13 @@ namespace osu.Game.Overlays.Mods public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; - private InputManager inputManager = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - inputManager = GetContainingInputManager()!; - } - protected override void OnHoverLost(HoverLostEvent e) { - if (ExpandedByHovering.Value && !hasHoveredchild()) + if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) Parent?.UpdateHoverExpansion(false); base.OnHoverLost(e); } - - private bool hasHoveredchild() - { - return inputManager.HoveredDrawables.Any(parentIsThis); - - bool parentIsThis(Drawable? d) - => d is not null && (d == this || parentIsThis(d.Parent)); - } } } } From 7f4bfb25a9991a3a4a6145e29c0951a35f97bd99 Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:24:32 +0800 Subject: [PATCH 369/670] Implement unit test --- .../Visual/Online/TestSceneDrawableChannel.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 4830c7b856..798bf48175 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -4,10 +4,15 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osu.Game.Overlays; using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online @@ -30,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online { RelativeSizeAxes = Axes.Both }); + Logger.Log("v.dwadwaawddwa"); + } [Test] @@ -83,5 +90,43 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("three day separators present", () => drawableChannel.ChildrenOfType().Count() == 3); AddAssert("last day separator is from correct day", () => drawableChannel.ChildrenOfType().Last().Date.Date == new DateTime(2022, 11, 22)); } + + [Test] + public void TestBackgroundAltering() + { + var localUser = new APIUser + { + Id = 3, + Username = "LocalUser" + }; + + string uuid = Guid.NewGuid().ToString(); + + int messageCount = 1; + + AddRepeatStep($"add messages", () => + { + channel.AddNewMessages(new Message(messageCount) + { + Sender = localUser, + Content = "Hi there all!", + Timestamp = new DateTimeOffset(2022, 11, 21, 20, 11, 13, TimeSpan.Zero), + Uuid = uuid, + }); + messageCount++; + }, 10); + + + AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType().Count() == 10); + + int checkCount = 0; + + AddRepeatStep("check background", () => + { + // +1 because the day separator take one index + Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlteringBackground); + checkCount++; + }, 10); + } } } From 73a98b45e94d0c289efecc7fd08f44c87953e358 Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:48:45 +0800 Subject: [PATCH 370/670] FIx code quality --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 8 ++------ osu.Game/Overlays/Chat/ChatLine.cs | 2 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 798bf48175..19f88826a7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -4,15 +4,11 @@ using System; using System.Linq; using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Testing; -using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -using osu.Game.Overlays; using osu.Game.Overlays.Chat; namespace osu.Game.Tests.Visual.Online @@ -47,6 +43,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3, Username = "LocalUser" }; + string uuid = Guid.NewGuid().ToString(); AddStep("add local echo message", () => channel.AddLocalEcho(new LocalEchoMessage { @@ -104,7 +101,7 @@ namespace osu.Game.Tests.Visual.Online int messageCount = 1; - AddRepeatStep($"add messages", () => + AddRepeatStep("add messages", () => { channel.AddNewMessages(new Message(messageCount) { @@ -116,7 +113,6 @@ namespace osu.Game.Tests.Visual.Online messageCount++; }, 10); - AddUntilStep("10 message present", () => drawableChannel.ChildrenOfType().Count() == 10); int checkCount = 0; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 922d040d54..fc43e38239 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -81,12 +81,14 @@ namespace osu.Game.Overlays.Chat alteringBackground = value; if (background == null) + { AddInternal(background = new Box { BypassAutoSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both, Colour = Color4.White, }); + } background.Alpha = value ? 0.04f : 0f; } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index c817417a44..8e353bfebd 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -107,8 +107,12 @@ namespace osu.Game.Overlays.Chat private void processMessageBackgroundAltering() { for (int i = 0; i < ChatLineFlow.Count(); i++) + { if (ChatLineFlow[i] is ChatLine chatline) + { chatline.AlteringBackground = i % 2 == 0; + } + } } protected override void Dispose(bool isDisposing) From 4e44a6e7f8fbb6f60cb6a67fc57f711fa4335b3c Mon Sep 17 00:00:00 2001 From: normalid Date: Sat, 27 Jul 2024 18:55:17 +0800 Subject: [PATCH 371/670] Clean up code --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 3 --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 19f88826a7..d0f9a8c69e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -31,8 +30,6 @@ namespace osu.Game.Tests.Visual.Online { RelativeSizeAxes = Axes.Both }); - Logger.Log("v.dwadwaawddwa"); - } [Test] diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 8e353bfebd..21af8d7305 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Chat private void processMessageBackgroundAltering() { - for (int i = 0; i < ChatLineFlow.Count(); i++) + for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { From 0c89210bd7f1e578476ce9e7d2c1d2f3df7f107c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:24:05 +0300 Subject: [PATCH 372/670] Add API models for daily challenge statistics --- .../Online/API/Requests/Responses/APIUser.cs | 3 ++ .../APIUserDailyChallengeStatistics.cs | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index a2836476c5..c69e45b3fd 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -272,6 +272,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("groups")] public APIUserGroup[] Groups; + [JsonProperty("daily_challenge_user_stats")] + public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics(); + public override string ToString() => Username; /// diff --git a/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs b/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs new file mode 100644 index 0000000000..e77f2b8f68 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUserDailyChallengeStatistics.cs @@ -0,0 +1,41 @@ +// 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 Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUserDailyChallengeStatistics + { + [JsonProperty("user_id")] + public int UserID; + + [JsonProperty("daily_streak_best")] + public int DailyStreakBest; + + [JsonProperty("daily_streak_current")] + public int DailyStreakCurrent; + + [JsonProperty("weekly_streak_best")] + public int WeeklyStreakBest; + + [JsonProperty("weekly_streak_current")] + public int WeeklyStreakCurrent; + + [JsonProperty("top_10p_placements")] + public int Top10PercentPlacements; + + [JsonProperty("top_50p_placements")] + public int Top50PercentPlacements; + + [JsonProperty("playcount")] + public int PlayCount; + + [JsonProperty("last_update")] + public DateTimeOffset? LastUpdate; + + [JsonProperty("last_weekly_streak")] + public DateTimeOffset? LastWeeklyStreak; + } +} From 17f5d58be2fcf5c2ead8d8b76a7b2ae5281d1197 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:24:29 +0300 Subject: [PATCH 373/670] Add daily challenge streak display and tooltip --- ...tSceneUserProfileDailyChallengeOverview.cs | 63 +++++ .../Components/DailyChallengeStreakDisplay.cs | 112 ++++++++ .../Components/DailyChallengeStreakTooltip.cs | 243 ++++++++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs new file mode 100644 index 0000000000..e2d26f222c --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs @@ -0,0 +1,63 @@ +// 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.Shapes; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Rulesets.Osu; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene + { + [Cached] + public readonly Bindable User = new Bindable(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo)); + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + protected override void LoadComplete() + { + base.LoadComplete(); + + DailyChallengeStreakDisplay display = null!; + + AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v)); + AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v)); + AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v)); + AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); + AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); + AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); + AddStep("create", () => + { + Clear(); + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }); + Add(display = new DailyChallengeStreakDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1f), + User = { BindTarget = User }, + }); + }); + AddStep("hover", () => InputManager.MoveMouseTo(display)); + } + + private void update(Action change) + { + change.Invoke(User.Value!.User.DailyChallengeStatistics); + User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs new file mode 100644 index 0000000000..2d9b107367 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -0,0 +1,112 @@ +// 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.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + { + public readonly Bindable User = new Bindable(); + + public APIUserDailyChallengeStatistics? TooltipContent { get; private set; } + + private OsuSpriteText dailyStreak = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + CornerRadius = 5; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5f), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + { + AutoSizeAxes = Axes.Both, + // Text = UsersStrings.ShowDailyChallengeTitle + Text = "Daily\nChallenge", + Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + CornerRadius = 5f, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6, + }, + dailyStreak = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + UseFullGlyphHeight = false, + Colour = colourProvider.Content2, + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + }, + } + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(_ => updateDisplay(), true); + } + + private void updateDisplay() + { + if (User.Value == null) + { + dailyStreak.Text = "-"; + return; + } + + var statistics = User.Value.User.DailyChallengeStatistics; + // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); + dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; + dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); + TooltipContent = statistics; + } + + public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(colourProvider); + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs new file mode 100644 index 0000000000..a95de8cefd --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -0,0 +1,243 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + { + [Cached] + private readonly OverlayColourProvider colourProvider; + + private StreakPiece currentDaily = null!; + private StreakPiece currentWeekly = null!; + private StatisticsPiece bestDaily = null!; + private StatisticsPiece bestWeekly = null!; + private StatisticsPiece topTen = null!; + private StatisticsPiece topFifty = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public DailyChallengeStreakTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + CornerRadius = 20f; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(15f), + Spacing = new Vector2(30f), + Children = new[] + { + // currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), + // currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), + currentDaily = new StreakPiece("Current Daily Streak"), + currentWeekly = new StreakPiece("Current Weekly Streak"), + } + }, + } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(15f), + Spacing = new Vector2(10f), + Children = new[] + { + // bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), + // bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), + // topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), + // topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), + bestDaily = new StatisticsPiece("Best Daily Streak"), + bestWeekly = new StatisticsPiece("Best Weekly Streak"), + topTen = new StatisticsPiece("Top 10% Placements"), + topFifty = new StatisticsPiece("Top 50% Placements"), + } + }, + } + } + }; + } + + public void SetContent(APIUserDailyChallengeStatistics content) + { + // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); + currentDaily.Value = $"{content.DailyStreakCurrent:N0}d"; + currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakCurrent)); + + // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = $"{content.WeeklyStreakCurrent:N0}w"; + currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakCurrent)); + + // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = $"{content.DailyStreakBest:N0}d"; + bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakBest)); + + // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = $"{content.WeeklyStreakBest:N0}w"; + bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakBest)); + + topTen.Value = content.Top10PercentPlacements.ToLocalisableString(@"N0"); + topFifty.Value = content.Top50PercentPlacements.ToLocalisableString(@"N0"); + } + + // reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43 + public static RankingTier TierForDaily(int daily) + { + if (daily > 360) + return RankingTier.Lustrous; + + if (daily > 240) + return RankingTier.Radiant; + + if (daily > 120) + return RankingTier.Rhodium; + + if (daily > 60) + return RankingTier.Platinum; + + if (daily > 30) + return RankingTier.Gold; + + if (daily > 10) + return RankingTier.Silver; + + if (daily > 5) + return RankingTier.Bronze; + + return RankingTier.Iron; + } + + public static RankingTier TierForWeekly(int weekly) => TierForDaily((weekly - 1) * 7); + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private partial class StreakPiece : FillFlowContainer + { + private readonly OsuSpriteText valueText; + + public LocalisableString Value + { + set => valueText.Text = value; + } + + public ColourInfo ValueColour + { + set => valueText.Colour = value; + } + + public StreakPiece(LocalisableString title) + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Vertical; + + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = title, + }, + valueText = new OsuSpriteText + { + // Colour = colour + Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), + } + }; + } + } + + private partial class StatisticsPiece : CompositeDrawable + { + private readonly OsuSpriteText valueText; + + public LocalisableString Value + { + set => valueText.Text = value; + } + + public ColourInfo ValueColour + { + set => valueText.Colour = value; + } + + public StatisticsPiece(LocalisableString title) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12), + Text = title, + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + valueText.Colour = colourProvider.Content2; + } + } + } +} From e82c54a31cf06fe64ff4a6fad31f7d9eff6aff19 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:57:30 +0300 Subject: [PATCH 374/670] Integrate daily challenge streak display with user profile overlay --- .../Online/TestSceneUserProfileOverlay.cs | 9 ++++ .../Profile/Header/Components/MainDetails.cs | 41 ++++++++++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 8dbd493920..937e08cb97 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -282,6 +282,15 @@ namespace osu.Game.Tests.Visual.Online ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png", }, }, + DailyChallengeStatistics = new APIUserDailyChallengeStatistics + { + DailyStreakCurrent = 231, + WeeklyStreakCurrent = 18, + DailyStreakBest = 370, + WeeklyStreakBest = 51, + Top10PercentPlacements = 345, + Top50PercentPlacements = 427, + }, Title = "osu!volunteer", Colour = "ff0000", Achievements = Array.Empty(), diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 2505c1bc8c..f9a4267ed9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -44,22 +44,41 @@ namespace osu.Game.Overlays.Profile.Header.Components Spacing = new Vector2(0, 15), Children = new Drawable[] { - new FillFlowContainer + new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20), - Children = new Drawable[] + ColumnDimensions = new[] { - detailGlobalRank = new ProfileValueDisplay(true) + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 20), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] { - Title = UsersStrings.ShowRankGlobalSimple, - }, - detailCountryRank = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankCountrySimple, - }, + detailGlobalRank = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankGlobalSimple, + }, + Empty(), + detailCountryRank = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankCountrySimple, + }, + new DailyChallengeStreakDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + User = { BindTarget = User }, + } + } } }, new Container From 31787757efefd9c2d0bc9f9f2dfb92942783f4da Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:21:21 +0300 Subject: [PATCH 375/670] Provide colour scheme as part of tooltip data to handle reusing tooltip with different profile hues --- .../Components/DailyChallengeStreakDisplay.cs | 9 ++- .../Components/DailyChallengeStreakTooltip.cs | 64 +++++++++---------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index 2d9b107367..87f833d165 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -10,15 +10,14 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip { public readonly Bindable User = new Bindable(); - public APIUserDailyChallengeStatistics? TooltipContent { get; private set; } + public DailyChallengeStreakTooltipData? TooltipContent { get; private set; } private OsuSpriteText dailyStreak = null!; @@ -104,9 +103,9 @@ namespace osu.Game.Overlays.Profile.Header.Components // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); - TooltipContent = statistics; + TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); } - public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(colourProvider); + public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index a95de8cefd..9dc4dfcb9c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -17,11 +17,8 @@ using osuTK; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip { - [Cached] - private readonly OverlayColourProvider colourProvider; - private StreakPiece currentDaily = null!; private StreakPiece currentWeekly = null!; private StatisticsPiece bestDaily = null!; @@ -29,14 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components private StatisticsPiece topTen = null!; private StatisticsPiece topFifty = null!; + private Box topBackground = null!; + private Box background = null!; + [Resolved] private OsuColour colours { get; set; } = null!; - public DailyChallengeStreakTooltip(OverlayColourProvider colourProvider) - { - this.colourProvider = colourProvider; - } - [BackgroundDependencyLoader] private void load() { @@ -46,10 +41,9 @@ namespace osu.Game.Overlays.Profile.Header.Components Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, }, new FillFlowContainer { @@ -62,10 +56,9 @@ namespace osu.Game.Overlays.Profile.Header.Components AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + topBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background5, }, new FillFlowContainer { @@ -106,26 +99,35 @@ namespace osu.Game.Overlays.Profile.Header.Components }; } - public void SetContent(APIUserDailyChallengeStatistics content) + public void SetContent(DailyChallengeStreakTooltipData content) { + var statistics = content.Statistics; + var colourProvider = content.ColourProvider; + + background.Colour = colourProvider.Background4; + topBackground.Colour = colourProvider.Background5; + // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); - currentDaily.Value = $"{content.DailyStreakCurrent:N0}d"; - currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakCurrent)); + currentDaily.Value = $"{statistics.DailyStreakCurrent:N0}d"; + currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); - // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakCurrent.ToLocalisableString(@"N0")); - currentWeekly.Value = $"{content.WeeklyStreakCurrent:N0}w"; - currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakCurrent)); + // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = $"{statistics.WeeklyStreakCurrent:N0}w"; + currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakBest.ToLocalisableString(@"N0")); - bestDaily.Value = $"{content.DailyStreakBest:N0}d"; - bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakBest)); + // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = $"{statistics.DailyStreakBest:N0}d"; + bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakBest.ToLocalisableString(@"N0")); - bestWeekly.Value = $"{content.WeeklyStreakBest:N0}w"; - bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakBest)); + // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = $"{statistics.WeeklyStreakBest:N0}w"; + bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); - topTen.Value = content.Top10PercentPlacements.ToLocalisableString(@"N0"); - topFifty.Value = content.Top50PercentPlacements.ToLocalisableString(@"N0"); + topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); + topTen.ValueColour = colourProvider.Content2; + + topFifty.Value = statistics.Top50PercentPlacements.ToLocalisableString(@"N0"); + topFifty.ValueColour = colourProvider.Content2; } // reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43 @@ -232,12 +234,8 @@ namespace osu.Game.Overlays.Profile.Header.Components } }; } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - valueText.Colour = colourProvider.Content2; - } } } + + public record DailyChallengeStreakTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); } From 6bdb1107c157a8feb8ded4a383dced04e05026e9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:34:59 +0300 Subject: [PATCH 376/670] Add shadow over tooltip --- .../Header/Components/DailyChallengeStreakTooltip.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 9dc4dfcb9c..02be0f2c99 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -2,18 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Effects; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osuTK; +using Box = osu.Framework.Graphics.Shapes.Box; +using Color4 = osuTK.Graphics.Color4; namespace osu.Game.Overlays.Profile.Header.Components { @@ -39,6 +42,13 @@ namespace osu.Game.Overlays.Profile.Header.Components CornerRadius = 20f; Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.5f), + Radius = 30f, + }; + Children = new Drawable[] { background = new Box From 82fbd5b045b2487c70e69fa0bf3fdd956b81967b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 06:40:16 +0300 Subject: [PATCH 377/670] Rename file --- ...ChallengeOverview.cs => TestSceneUserProfileDailyChallenge.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneUserProfileDailyChallengeOverview.cs => TestSceneUserProfileDailyChallenge.cs} (100%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs similarity index 100% rename from osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs From 7fedfd368c83767846e947372e9fba03e07f6ceb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 07:22:58 +0300 Subject: [PATCH 378/670] Fix score breakdown tooltips appearing in other feeds --- .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index cfec170cf6..12401061a3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private FillFlowContainer barsContainer = null!; + // we're always present so that we can update while hidden, but we don't want tooltips to be displayed, therefore directly use alpha comparison here. + public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && Alpha > 0; + private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; private long[] bins = new long[bin_count]; From f6eb9037df1d1f2bfd3d2285c20752923403f3d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Jul 2024 23:50:52 -0700 Subject: [PATCH 379/670] Add ability to copy leaderboard mods in daily challenge --- .../DailyChallenge/DailyChallenge.cs | 6 +++-- .../DailyChallengeLeaderboard.cs | 20 +++++++++++++++++ .../Leaderboards/LeaderboardScoreV2.cs | 22 ++++++++++++++----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8538cbbb59..e1f78129a4 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -21,6 +21,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Online.API; @@ -168,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }, null, [ - new Container + new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -238,6 +239,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { RelativeSizeAxes = Axes.Both, PresentScore = presentScore, + SelectedMods = { BindTarget = userMods }, }, // Spacer null, @@ -329,7 +331,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge var rulesetInstance = rulesets.GetRuleset(playlistItem.RulesetID)!.CreateInstance(); var allowedMods = playlistItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)); - userModsSelectOverlay.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); + userModsSelectOverlay.IsValidMod = leaderboard.IsValidMod = m => allowedMods.Any(a => a.GetType() == m.GetType()); } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index 5efb656cea..f332e717c1 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Allocation; @@ -14,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens.SelectV2.Leaderboards; using osuTK; @@ -24,6 +26,20 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public IBindable UserBestScore => userBestScore; private readonly Bindable userBestScore = new Bindable(); + public Bindable> SelectedMods = new Bindable>(); + + private Func isValidMod = _ => true; + + /// + /// A function determining whether each mod in the score can be selected. + /// A return value of means that the mod can be selected in the current context. + /// A return value of means that the mod cannot be selected in the current context. + /// + public Func IsValidMod + { + get => isValidMod; + set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + } public Action? PresentScore { get; init; } @@ -153,6 +169,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Rank = index + 1, IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), + SelectedMods = { BindTarget = SelectedMods }, + IsValidMod = isValidMod, }), loaded => { scoreFlow.Clear(); @@ -171,6 +189,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Rank = userBest.Position, IsPersonalBest = true, Action = () => PresentScore?.Invoke(userBest.OnlineID), + SelectedMods = { BindTarget = SelectedMods }, + IsValidMod = isValidMod, }); } diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index 700f889d7f..6066bc7739 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -43,6 +43,21 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { public partial class LeaderboardScoreV2 : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip { + public Bindable> SelectedMods = new Bindable>(); + + private Func isValidMod = _ => true; + + /// + /// A function determining whether each mod in the score can be selected. + /// A return value of means that the mod can be selected in the current context. + /// A return value of means that the mod cannot be selected in the current context. + /// + public Func IsValidMod + { + get => isValidMod; + set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); + } + public int? Rank { get; init; } public bool IsPersonalBest { get; init; } @@ -68,9 +83,6 @@ namespace osu.Game.Screens.SelectV2.Leaderboards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - [Resolved] - private SongSelect? songSelect { get; set; } - [Resolved] private IDialogOverlay? dialogOverlay { get; set; } @@ -738,8 +750,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { List items = new List(); - if (score.Mods.Length > 0 && songSelect != null) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); + if (score.Mods.Length > 0) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => isValidMod.Invoke(m)).ToArray())); if (score.Files.Count <= 0) return items.ToArray(); From e58bdbb8a944b0c73623d3ab0867b51f484fab04 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 15:08:36 +0800 Subject: [PATCH 380/670] Improve unit test --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index d0f9a8c69e..795d49adad 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -94,8 +94,6 @@ namespace osu.Game.Tests.Visual.Online Username = "LocalUser" }; - string uuid = Guid.NewGuid().ToString(); - int messageCount = 1; AddRepeatStep("add messages", () => @@ -104,8 +102,8 @@ namespace osu.Game.Tests.Visual.Online { Sender = localUser, Content = "Hi there all!", - Timestamp = new DateTimeOffset(2022, 11, 21, 20, 11, 13, TimeSpan.Zero), - Uuid = uuid, + Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero), + Uuid = Guid.NewGuid().ToString(), }); messageCount++; }, 10); From 5db0e3640436eaf5fe58f21d98f5ea6d19e3b335 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 16:18:43 +0800 Subject: [PATCH 381/670] Use the `TruncatingSpriteText` in `ModPresetTooltip` --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index ec81aa7ceb..8f4efa7667 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -44,10 +44,12 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(7), Children = new[] { - descriptionText = new OsuSpriteText + descriptionText = new TruncatingSpriteText { + RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(weight: FontWeight.Regular), Colour = colourProvider.Content1, + AllowMultiline = true, }, } } From 4e65944609d7c907fa10c8f9af7e376115dcefe2 Mon Sep 17 00:00:00 2001 From: normalid Date: Sun, 28 Jul 2024 16:26:18 +0800 Subject: [PATCH 382/670] Make the tooltips width be dyanmic with the content, so the long text wont occurs wierd line break --- osu.Game/Overlays/Mods/ModPresetRow.cs | 3 +-- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 17 +++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetRow.cs b/osu.Game/Overlays/Mods/ModPresetRow.cs index 4829e93b87..8614806085 100644 --- a/osu.Game/Overlays/Mods/ModPresetRow.cs +++ b/osu.Game/Overlays/Mods/ModPresetRow.cs @@ -24,8 +24,7 @@ namespace osu.Game.Overlays.Mods { new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(7), Children = new Drawable[] diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 8f4efa7667..768feb0756 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -23,8 +23,7 @@ namespace osu.Game.Overlays.Mods public ModPresetTooltip(OverlayColourProvider colourProvider) { - Width = 250; - AutoSizeAxes = Axes.Y; + AutoSizeAxes = Axes.Both; Masking = true; CornerRadius = 7; @@ -38,18 +37,16 @@ namespace osu.Game.Overlays.Mods }, Content = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Spacing = new Vector2(7), Children = new[] { - descriptionText = new TruncatingSpriteText + descriptionText = new OsuSpriteText { - RelativeSizeAxes = Axes.X, Font = OsuFont.GetFont(weight: FontWeight.Regular), Colour = colourProvider.Content1, - AllowMultiline = true, }, } } @@ -68,7 +65,11 @@ namespace osu.Game.Overlays.Mods lastPreset = preset; Content.RemoveAll(d => d is ModPresetRow, true); - Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod))); + Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod) + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + })); } protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); From 1c9c3c92fdf539b485bc91173e931d08396de904 Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:30:42 -0400 Subject: [PATCH 383/670] Add tests for expected timestamp format --- osu.Game.Tests/Editing/EditorTimestampParserTest.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs index 9c7fae0eaf..cb9bd3dafe 100644 --- a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs +++ b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs @@ -16,7 +16,6 @@ namespace osu.Game.Tests.Editing new object?[] { "1", true, TimeSpan.FromMilliseconds(1), null }, new object?[] { "99", true, TimeSpan.FromMilliseconds(99), null }, new object?[] { "320000", true, TimeSpan.FromMilliseconds(320000), null }, - new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:92", false, null, null }, new object?[] { "1:002", false, null, null }, @@ -25,6 +24,9 @@ namespace osu.Game.Tests.Editing new object?[] { "1:02:3000", false, null, null }, new object?[] { "1:02:300 ()", false, null, null }, new object?[] { "1:02:300 (1,2,3)", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - ", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - following mod", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, + new object?[] { "1:02:300 (1,2,3) - following mod\nwith newlines", true, new TimeSpan(0, 0, 1, 2, 300), "1,2,3" }, }; [TestCaseSource(nameof(test_cases))] From dec6b190f249677f9a9e37a477547f8a6474dff9 Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:31:36 -0400 Subject: [PATCH 384/670] Add optional 'suffix' to timestamp --- osu.Game/Rulesets/Edit/EditorTimestampParser.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs index 92a692b94e..9e637e55bc 100644 --- a/osu.Game/Rulesets/Edit/EditorTimestampParser.cs +++ b/osu.Game/Rulesets/Edit/EditorTimestampParser.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Edit { /// /// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat) - /// Original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 + /// Original osu-web regex: + /// https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78 /// /// /// 00:00:000 (...) - test @@ -32,7 +33,10 @@ namespace osu.Game.Rulesets.Edit /// 1:02:300 (1,2,3) - parses to 01:02:300 with selection /// /// - private static readonly Regex time_regex_lenient = new Regex(@"^(((?\d{1,3}):(?([0-5]?\d))([:.](?\d{0,3}))?)(?\s\([^)]+\))?)$", RegexOptions.Compiled); + private static readonly Regex time_regex_lenient = new Regex( + @"^(((?\d{1,3}):(?([0-5]?\d))([:.](?\d{0,3}))?)(?\s\([^)]+\))?)(?\s-.*)?$", + RegexOptions.Compiled | RegexOptions.Singleline + ); public static bool TryParse(string timestamp, [NotNullWhen(true)] out TimeSpan? parsedTime, out string? parsedSelection) { From ae61df0abe507f675282be5d54146c9e1736a27b Mon Sep 17 00:00:00 2001 From: Shreyas Kadambi Date: Sun, 28 Jul 2024 11:47:00 -0400 Subject: [PATCH 385/670] Add back accidentally removed test --- osu.Game.Tests/Editing/EditorTimestampParserTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs index cb9bd3dafe..49154f1cbb 100644 --- a/osu.Game.Tests/Editing/EditorTimestampParserTest.cs +++ b/osu.Game.Tests/Editing/EditorTimestampParserTest.cs @@ -16,6 +16,7 @@ namespace osu.Game.Tests.Editing new object?[] { "1", true, TimeSpan.FromMilliseconds(1), null }, new object?[] { "99", true, TimeSpan.FromMilliseconds(99), null }, new object?[] { "320000", true, TimeSpan.FromMilliseconds(320000), null }, + new object?[] { "1:2", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:02", true, new TimeSpan(0, 0, 1, 2), null }, new object?[] { "1:92", false, null, null }, new object?[] { "1:002", false, null, null }, From 63757a77a54a108c56d9538c229daa15c81c1b7b Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 13:39:08 +0800 Subject: [PATCH 386/670] Extract update background method --- osu.Game/Overlays/Chat/ChatLine.cs | 28 +++++++++++++---------- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index fc43e38239..81b63d3380 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -79,18 +79,7 @@ namespace osu.Game.Overlays.Chat set { alteringBackground = value; - - if (background == null) - { - AddInternal(background = new Box - { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } - - background.Alpha = value ? 0.04f : 0f; + updateBackground(); } } @@ -283,5 +272,20 @@ namespace osu.Game.Overlays.Chat Color4Extensions.FromHex("812a96"), Color4Extensions.FromHex("992861"), }; + + private void updateBackground() + { + if (background == null) + { + AddInternal(background = new Box + { + BypassAutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + } + + background.Alpha = alteringBackground ? 0.04f : 0f; + } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 21af8d7305..b6b89c4201 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Chat highlightedMessage.Value = null; }); - private void processMessageBackgroundAltering() + private void processChatlineBackgroundAltering() { for (int i = 0; i < ChatLineFlow.Count; i++) { @@ -169,7 +169,7 @@ namespace osu.Game.Overlays.Chat scroll.ScrollToEnd(); processMessageHighlighting(); - processMessageBackgroundAltering(); + processChatlineBackgroundAltering(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 54c904d439ae2b6b37b97d29768ad8e04994d054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:40:29 +0200 Subject: [PATCH 387/670] Convert into auto-property --- .../SelectV2/Leaderboards/LeaderboardScoreV2.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index 6066bc7739..c9584b057b 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -45,18 +45,12 @@ namespace osu.Game.Screens.SelectV2.Leaderboards { public Bindable> SelectedMods = new Bindable>(); - private Func isValidMod = _ => true; - /// /// A function determining whether each mod in the score can be selected. /// A return value of means that the mod can be selected in the current context. /// A return value of means that the mod cannot be selected in the current context. /// - public Func IsValidMod - { - get => isValidMod; - set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - } + public Func IsValidMod { get; set; } = _ => true; public int? Rank { get; init; } public bool IsPersonalBest { get; init; } @@ -751,7 +745,7 @@ namespace osu.Game.Screens.SelectV2.Leaderboards List items = new List(); if (score.Mods.Length > 0) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => isValidMod.Invoke(m)).ToArray())); + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => SelectedMods.Value = score.Mods.Where(m => IsValidMod.Invoke(m)).ToArray())); if (score.Files.Count <= 0) return items.ToArray(); From 861b5465628cff64ca1efed3883d0978725bb61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:45:03 +0200 Subject: [PATCH 388/670] Add vague test coverage --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 36e256b920..91df38feb9 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -115,6 +115,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay MaxCombo = 1000, TotalScore = 1000000, User = new APIUser { Username = "best user" }, + Mods = [new APIMod { Acronym = @"DT" }], Statistics = new Dictionary() }, new MultiplayerScore From 2ff0a89b4fda97b4fb4b6a634376b5ac4b629b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 10:59:21 +0200 Subject: [PATCH 389/670] Convert into auto-property even more --- .../DailyChallenge/DailyChallengeLeaderboard.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs index f332e717c1..c9152393e7 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeLeaderboard.cs @@ -28,18 +28,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private readonly Bindable userBestScore = new Bindable(); public Bindable> SelectedMods = new Bindable>(); - private Func isValidMod = _ => true; - /// /// A function determining whether each mod in the score can be selected. /// A return value of means that the mod can be selected in the current context. /// A return value of means that the mod cannot be selected in the current context. /// - public Func IsValidMod - { - get => isValidMod; - set => isValidMod = value ?? throw new ArgumentNullException(nameof(value)); - } + public Func IsValidMod { get; set; } = _ => true; public Action? PresentScore { get; init; } @@ -170,7 +164,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge IsPersonalBest = s.UserID == api.LocalUser.Value.Id, Action = () => PresentScore?.Invoke(s.OnlineID), SelectedMods = { BindTarget = SelectedMods }, - IsValidMod = isValidMod, + IsValidMod = IsValidMod, }), loaded => { scoreFlow.Clear(); @@ -190,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge IsPersonalBest = true, Action = () => PresentScore?.Invoke(userBest.OnlineID), SelectedMods = { BindTarget = SelectedMods }, - IsValidMod = isValidMod, + IsValidMod = IsValidMod, }); } From 5ec46a79b4d002f78b14f549998ae7fef447bc85 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 17:50:23 +0800 Subject: [PATCH 390/670] Only create a new drawable object when the background is needed --- osu.Game/Overlays/Chat/ChatLine.cs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 81b63d3380..486beb58b7 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -21,7 +21,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osuTK.Graphics; -using Message = osu.Game.Online.Chat.Message; namespace osu.Game.Overlays.Chat { @@ -118,6 +117,11 @@ namespace osu.Game.Overlays.Chat InternalChild = new GridContainer { + Margin = new MarginPadding + { + Horizontal = 10, + Vertical = 1, + }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, @@ -275,17 +279,23 @@ namespace osu.Game.Overlays.Chat private void updateBackground() { - if (background == null) + if (alteringBackground) { - AddInternal(background = new Box + if (background?.IsAlive != true) { - BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } + AddInternal(background = new Circle + { + MaskingSmoothness = 2.5f, + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }); + } - background.Alpha = alteringBackground ? 0.04f : 0f; + background.Alpha = 0.04f; + } + else + background?.Expire(); } } } From 9b96bd1d730ac95456650c530ba6d6a4afeb6d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 11:53:06 +0200 Subject: [PATCH 391/670] Force exit to main menu when presenting scores from within playlists / multiplayer - Closes https://github.com/ppy/osu/issues/29152 - Partially reverts https://github.com/ppy/osu/pull/29097 - Reopens https://github.com/ppy/osu/issues/26666 When testing I failed to predict that in multiplayer there can be a different beatmap in the playlist queue. If this is the case, `PresentScore()` will exit out to `Multiplayer`, whose `RoomSubScreen` will update the selected item - and thus, the global beatmap - to the next item in queue, at which point trying to play games with "not touching the global beatmap bindable if we don't need to" fail to work, because the bindable *must* be touched for correct operation, yet it cannot (because `OnlinePlayScreen`s disable it). I'm not sure what the fix is here: - making replay player somehow independent of the global beatmap? - not exiting out to multiplayer, but instead doing the present from the results screen itself? if so, then how to ensure the screen stack can't overflow to infinity? so I'm just reverting the broken part. The daily challenge part is left in because as is it should not cause issues. --- osu.Game/OsuGame.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2195576be1..53b2fd5904 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -63,7 +63,6 @@ using osu.Game.Screens; using osu.Game.Screens.Edit; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; -using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; @@ -757,11 +756,13 @@ namespace osu.Game // As a special case, if the beatmap and ruleset already match, allow immediately displaying the score from song select. // This is guaranteed to not crash, and feels better from a user's perspective (ie. if they are clicking a score in the // song select leaderboard). - // Similar exemptions are made here for online flows where there are good chances that beatmap and ruleset match - // (playlists / multiplayer / daily challenge). + // Similar exemptions are made here for daily challenge where it is guaranteed that beatmap and ruleset match. + // `OnlinePlayScreen` is excluded because when resuming back to it, + // `RoomSubScreen` changes the global beatmap to the next playlist item on resume, + // which may not match the score, and thus crash. IEnumerable validScreens = Beatmap.Value.BeatmapInfo.Equals(databasedBeatmap) && Ruleset.Value.Equals(databasedScore.ScoreInfo.Ruleset) - ? new[] { typeof(SongSelect), typeof(OnlinePlayScreen), typeof(DailyChallenge) } + ? new[] { typeof(SongSelect), typeof(DailyChallenge) } : Array.Empty(); PerformFromScreen(screen => From 90fdf5599fbb6943ef62b2fd5eafe66d954e7bc2 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 18:14:07 +0800 Subject: [PATCH 392/670] Revert changes --- osu.Game/Overlays/Mods/ModPresetRow.cs | 3 ++- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetRow.cs b/osu.Game/Overlays/Mods/ModPresetRow.cs index 8614806085..4829e93b87 100644 --- a/osu.Game/Overlays/Mods/ModPresetRow.cs +++ b/osu.Game/Overlays/Mods/ModPresetRow.cs @@ -24,7 +24,8 @@ namespace osu.Game.Overlays.Mods { new FillFlowContainer { - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, Spacing = new Vector2(7), Children = new Drawable[] diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 768feb0756..ec81aa7ceb 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -23,7 +23,8 @@ namespace osu.Game.Overlays.Mods public ModPresetTooltip(OverlayColourProvider colourProvider) { - AutoSizeAxes = Axes.Both; + Width = 250; + AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 7; @@ -37,8 +38,8 @@ namespace osu.Game.Overlays.Mods }, Content = new FillFlowContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = 10, Right = 10, Top = 5, Bottom = 5 }, Spacing = new Vector2(7), Children = new[] @@ -65,11 +66,7 @@ namespace osu.Game.Overlays.Mods lastPreset = preset; Content.RemoveAll(d => d is ModPresetRow, true); - Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod) - { - RelativeSizeAxes = Axes.None, - AutoSizeAxes = Axes.Both, - })); + Content.AddRange(preset.Mods.AsOrdered().Select(mod => new ModPresetRow(mod))); } protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); From 8f8668111077cbb75a29370a4735da60e79cba2e Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 18:29:44 +0800 Subject: [PATCH 393/670] Replace `OsuSpriteText` with `TextFlowContainer` --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index ec81aa7ceb..6dafe85d89 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; @@ -19,7 +18,7 @@ namespace osu.Game.Overlays.Mods private const double transition_duration = 200; - private readonly OsuSpriteText descriptionText; + private readonly TextFlowContainer descriptionText; public ModPresetTooltip(OverlayColourProvider colourProvider) { @@ -44,11 +43,11 @@ namespace osu.Game.Overlays.Mods Spacing = new Vector2(7), Children = new[] { - descriptionText = new OsuSpriteText + descriptionText = new TextFlowContainer(f => { - Font = OsuFont.GetFont(weight: FontWeight.Regular), - Colour = colourProvider.Content1, - }, + f.Font = OsuFont.GetFont(weight: FontWeight.Regular); + f.Colour = colourProvider.Content1; + }) } } }; From f1a84a5111748a59ee72973cd379850c199751d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 12:52:11 +0200 Subject: [PATCH 394/670] Fix mods persisting after watching replay from daily challenge screen Closes https://github.com/ppy/osu/issues/29133. Hope I can be forgiven for no tests. I had a brief try but writing them is going to take hours. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 4b4e4a7a62..322d855cd3 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -454,6 +454,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.OnResuming(e); applyLoopingToTrack(); + // re-apply mods as they may have been changed by a child screen + // (one known instance of this is showing a replay). + updateMods(); } public override void OnSuspending(ScreenTransitionEvent e) From c142adf926795b6714311657953edcba8c7ad9d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 19:58:19 +0900 Subject: [PATCH 395/670] Fix online status not persisting correctly Regressed at some point. I don't see much reason not to link the bindable directly with config. It seems to work as you'd expect. Tested with logout (resets to "Online") and connection failure (persists). Closes https://github.com/ppy/osu/issues/29173. --- osu.Game/Online/API/APIAccess.cs | 7 +++---- osu.Game/Overlays/Login/LoginPanel.cs | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 0cf344ecaf..c02ca1bf5e 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -118,12 +118,11 @@ namespace osu.Game.Online.API u.OldValue?.Activity.UnbindFrom(activity); u.NewValue.Activity.BindTo(activity); - if (u.OldValue != null) - localUserStatus.UnbindFrom(u.OldValue.Status); - localUserStatus.BindTo(u.NewValue.Status); + u.OldValue?.Status.UnbindFrom(localUserStatus); + u.NewValue.Status.BindTo(localUserStatus); }, true); - localUserStatus.BindValueChanged(val => configStatus.Value = val.NewValue); + localUserStatus.BindTo(configStatus); var thread = new Thread(run) { diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index cb642f9b72..84bd0c36b9 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -157,6 +157,7 @@ namespace osu.Game.Overlays.Login }, }; + updateDropdownCurrent(status.Value); dropdown.Current.BindValueChanged(action => { switch (action.NewValue) From 11265538c484fa22a23c49dc994faac96d8a2bca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:02:18 +0900 Subject: [PATCH 396/670] Reset online status on logout --- osu.Game/Online/API/APIAccess.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index c02ca1bf5e..716d1e4466 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -599,6 +599,7 @@ namespace osu.Game.Online.API password = null; SecondFactorCode = null; authentication.Clear(); + configStatus.Value = UserStatus.Online; // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present Schedule(() => From d51a53b051d46c58179f3774d0ea195f4705368c Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 29 Jul 2024 19:08:14 +0800 Subject: [PATCH 397/670] Preventing the mod icon being squashed up --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 6dafe85d89..6ffcfca1e0 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -48,6 +48,10 @@ namespace osu.Game.Overlays.Mods f.Font = OsuFont.GetFont(weight: FontWeight.Regular); f.Colour = colourProvider.Content1; }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } } } }; From 8b96b0b9e497f93c5860ad1366b1d7db3363c324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 13:19:01 +0200 Subject: [PATCH 398/670] Add logging when starting and stopping watch operations in online metadata client For future use with debugging issues like https://github.com/ppy/osu/issues/29138, hopefully. --- osu.Game/Online/Metadata/OnlineMetadataClient.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 911b13ecd8..a3041c6753 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -215,6 +215,7 @@ namespace osu.Game.Online.Metadata Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingUserPresence)).ConfigureAwait(false); Schedule(() => isWatchingUserPresence.Value = true); + Logger.Log($@"{nameof(OnlineMetadataClient)} began watching user presence", LoggingTarget.Network); } public override async Task EndWatchingUserPresence() @@ -228,6 +229,7 @@ namespace osu.Game.Online.Metadata Schedule(() => userStates.Clear()); Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingUserPresence)).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching user presence", LoggingTarget.Network); } finally { @@ -247,7 +249,9 @@ namespace osu.Game.Online.Metadata throw new OperationCanceledException(); Debug.Assert(connection != null); - return await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false); + var result = await connection.InvokeAsync(nameof(IMetadataServer.BeginWatchingMultiplayerRoom), id).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} began watching multiplayer room with ID {id}", LoggingTarget.Network); + return result; } public override async Task EndWatchingMultiplayerRoom(long id) @@ -257,6 +261,7 @@ namespace osu.Game.Online.Metadata Debug.Assert(connection != null); await connection.InvokeAsync(nameof(IMetadataServer.EndWatchingMultiplayerRoom), id).ConfigureAwait(false); + Logger.Log($@"{nameof(OnlineMetadataClient)} stopped watching multiplayer room with ID {id}", LoggingTarget.Network); } public override async Task DisconnectRequested() From 997b3eb498ecdd689c4f68205bb90fdbd211a0a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:16:41 +0900 Subject: [PATCH 399/670] Fix typos and visuals --- .../Visual/Online/TestSceneDrawableChannel.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 126 +++++++++--------- osu.Game/Overlays/Chat/DrawableChannel.cs | 6 +- 3 files changed, 68 insertions(+), 66 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 795d49adad..bb73a458a3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("check background", () => { // +1 because the day separator take one index - Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlteringBackground); + Assert.AreEqual((checkCount + 1) % 2 == 0, drawableChannel.ChildrenOfType().ToList()[checkCount].AlternatingBackground); checkCount++; }, 10); } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 486beb58b7..e7be7e7814 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -70,14 +70,14 @@ namespace osu.Game.Overlays.Chat private Drawable? background; - private bool alteringBackground; + private bool alternatingBackground; - public bool AlteringBackground + public bool AlternatingBackground { - get => alteringBackground; + get => alternatingBackground; set { - alteringBackground = value; + alternatingBackground = value; updateBackground(); } } @@ -115,53 +115,70 @@ namespace osu.Game.Overlays.Chat configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); prefer24HourTime.BindValueChanged(_ => updateTimestamp()); - InternalChild = new GridContainer + InternalChildren = new[] { - Margin = new MarginPadding + background = new Container { - Horizontal = 10, - Vertical = 1, - }, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + Masking = true, + Blending = BlendingParameters.Additive, + CornerRadius = 4, + RelativeSizeAxes = Axes.Both, + Child = new Box { - drawableTimestamp = new OsuSpriteText - { - Shadow = false, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), - AlwaysPresent = true, - }, - drawableUsername = new DrawableChatUsername(message.Sender) - { - Width = UsernameWidth, - FontSize = FontSize, - AutoSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Margin = new MarginPadding { Horizontal = Spacing }, - AccentColour = UsernameColour, - Inverted = !string.IsNullOrEmpty(message.Sender.Colour), - }, - drawableContentFlow = new LinkFlowContainer(styleMessageContent) - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - } + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, }, + }, + new GridContainer + { + Margin = new MarginPadding + { + Horizontal = 10, + Vertical = 1, + }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + drawableTimestamp = new OsuSpriteText + { + Shadow = false, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), + AlwaysPresent = true, + }, + drawableUsername = new DrawableChatUsername(message.Sender) + { + Width = UsernameWidth, + FontSize = FontSize, + AutoSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Margin = new MarginPadding { Horizontal = Spacing }, + AccentColour = UsernameColour, + Inverted = !string.IsNullOrEmpty(message.Sender.Colour), + }, + drawableContentFlow = new LinkFlowContainer(styleMessageContent) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + } + }, + } } }; + + updateBackground(); } protected override void LoadComplete() @@ -279,23 +296,8 @@ namespace osu.Game.Overlays.Chat private void updateBackground() { - if (alteringBackground) - { - if (background?.IsAlive != true) - { - AddInternal(background = new Circle - { - MaskingSmoothness = 2.5f, - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }); - } - - background.Alpha = 0.04f; - } - else - background?.Expire(); + if (background != null) + background.Alpha = alternatingBackground ? 0.03f : 0; } } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index b6b89c4201..f5dd5a24f2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -104,13 +104,13 @@ namespace osu.Game.Overlays.Chat highlightedMessage.Value = null; }); - private void processChatlineBackgroundAltering() + private void updateBackgroundAlternating() { for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { - chatline.AlteringBackground = i % 2 == 0; + chatline.AlternatingBackground = i % 2 == 0; } } } @@ -169,7 +169,7 @@ namespace osu.Game.Overlays.Chat scroll.ScrollToEnd(); processMessageHighlighting(); - processChatlineBackgroundAltering(); + updateBackgroundAlternating(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 5bc02cc1c69e104dca9a44439218d77a91d93c55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:25:02 +0900 Subject: [PATCH 400/670] Fix background alternating not updating on message removal --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 23 +++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e7be7e7814..87e1f5699b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -77,6 +77,9 @@ namespace osu.Game.Overlays.Chat get => alternatingBackground; set { + if (alternatingBackground == value) + return; + alternatingBackground = value; updateBackground(); } @@ -122,6 +125,7 @@ namespace osu.Game.Overlays.Chat Masking = true, Blending = BlendingParameters.Additive, CornerRadius = 4, + Alpha = 0, RelativeSizeAxes = Axes.Both, Child = new Box { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f5dd5a24f2..6b3acaa226 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -84,6 +84,17 @@ namespace osu.Game.Overlays.Chat highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true); } + protected override void Update() + { + base.Update(); + + for (int i = 0; i < ChatLineFlow.Count; i++) + { + if (ChatLineFlow[i] is ChatLine chatline) + chatline.AlternatingBackground = i % 2 == 0; + } + } + /// /// Processes any pending message in . /// @@ -104,17 +115,6 @@ namespace osu.Game.Overlays.Chat highlightedMessage.Value = null; }); - private void updateBackgroundAlternating() - { - for (int i = 0; i < ChatLineFlow.Count; i++) - { - if (ChatLineFlow[i] is ChatLine chatline) - { - chatline.AlternatingBackground = i % 2 == 0; - } - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -169,7 +169,6 @@ namespace osu.Game.Overlays.Chat scroll.ScrollToEnd(); processMessageHighlighting(); - updateBackgroundAlternating(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 76cd2df6999866dde279387dfa9efe1ea4f04fc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:43:09 +0900 Subject: [PATCH 401/670] Add ability to test daily challenge carousel items when hidden --- .../DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs | 2 ++ .../DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs | 2 ++ .../DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs | 1 + 3 files changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 631aafb58f..086f3ce174 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge breakdown.Height = height; }); + AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0); + AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); AddStep("add new score", () => { diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs index 9e21214c11..baa1eb8318 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTimeRemainingRing.cs @@ -55,6 +55,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge if (ring.IsNotNull()) ring.Height = height; }); + AddToggleStep("toggle visible", v => ring.Alpha = v ? 1 : 0); + AddStep("just started", () => { room.Value.StartDate.Value = DateTimeOffset.Now.AddMinutes(-1); diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs index ba5a0989d4..ae212f5212 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeTotalsDisplay.cs @@ -49,6 +49,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge if (totals.IsNotNull()) totals.Height = height; }); + AddToggleStep("toggle visible", v => totals.Alpha = v ? 1 : 0); AddStep("set counts", () => totals.SetInitialCounts(totalPassCount: 9650, cumulativeTotalScore: 10_000_000_000)); From 5a1002c1a07b2108f73dad4731ecd506a82c6445 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:43:34 +0900 Subject: [PATCH 402/670] Ensure score breakdown doesn't spam scores when not visible --- .../DailyChallengeScoreBreakdown.cs | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 12401061a3..79ad77831b 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -74,30 +74,34 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); bins[targetBin] += 1; - updateCounts(); - var text = new OsuSpriteText - { - Text = newScoreEvent.TotalScore.ToString(@"N0"), - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Font = OsuFont.Default.With(size: 30), - RelativePositionAxes = Axes.X, - X = (targetBin + 0.5f) / bin_count - 0.5f, - Alpha = 0, - }; - AddInternal(text); + Scheduler.AddOnce(updateCounts); - Scheduler.AddDelayed(() => + if (Alpha > 0) { - float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y; - text.FadeInFromZero() - .ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf) - .MoveToY(startY) - .MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint) - .FadeOut(2500, Easing.OutQuint) - .Expire(); - }, 150); + var text = new OsuSpriteText + { + Text = newScoreEvent.TotalScore.ToString(@"N0"), + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Default.With(size: 30), + RelativePositionAxes = Axes.X, + X = (targetBin + 0.5f) / bin_count - 0.5f, + Alpha = 0, + }; + AddInternal(text); + + Scheduler.AddDelayed(() => + { + float startY = ToLocalSpace(barsContainer[targetBin].CircularBar.ScreenSpaceDrawQuad.TopLeft).Y; + text.FadeInFromZero() + .ScaleTo(new Vector2(0.8f), 500, Easing.OutElasticHalf) + .MoveToY(startY) + .MoveToOffset(new Vector2(0, -50), 2500, Easing.OutQuint) + .FadeOut(2500, Easing.OutQuint) + .Expire(); + }, 150); + } } public void SetInitialCounts(long[] counts) From 05056f0e8a107da1b7a54208f3b8aee9139679d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:37:52 +0900 Subject: [PATCH 403/670] Remove no longer required `AlwaysPresent` definition This also reverts commit 7fedfd368c83767846e947372e9fba03e07f6ceb as no-longer-necessary. --- .../OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs | 1 - .../OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs index a9f9a5cd78..09c0c3f017 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeCarousel.cs @@ -50,7 +50,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { drawable.RelativeSizeAxes = Axes.Both; drawable.Size = Vector2.One; - drawable.AlwaysPresent = true; drawable.Alpha = 0; base.Add(drawable); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index 79ad77831b..b35379e126 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -27,9 +27,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private FillFlowContainer barsContainer = null!; - // we're always present so that we can update while hidden, but we don't want tooltips to be displayed, therefore directly use alpha comparison here. - public override bool PropagatePositionalInputSubTree => base.PropagatePositionalInputSubTree && Alpha > 0; - private const int bin_count = MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS; private long[] bins = new long[bin_count]; From 7afcd728723b94996112bb34a6de6a96c7041dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 20:58:42 +0900 Subject: [PATCH 404/670] Fix potentially too many scores displaying in breakdown while in gameplay --- .../TestSceneDailyChallengeScoreBreakdown.cs | 33 ++++++++++++++-- .../DailyChallengeScoreBreakdown.cs | 39 ++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs index 086f3ce174..b04696aded 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeScoreBreakdown.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; @@ -20,11 +21,11 @@ namespace osu.Game.Tests.Visual.DailyChallenge [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); - [Test] - public void TestBasicAppearance() - { - DailyChallengeScoreBreakdown breakdown = null!; + private DailyChallengeScoreBreakdown breakdown = null!; + [SetUpSteps] + public void SetUpSteps() + { AddStep("create content", () => Children = new Drawable[] { new Box @@ -53,6 +54,11 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddToggleStep("toggle visible", v => breakdown.Alpha = v ? 1 : 0); AddStep("set initial data", () => breakdown.SetInitialCounts([1, 4, 9, 16, 25, 36, 49, 36, 25, 16, 9, 4, 1])); + } + + [Test] + public void TestBasicAppearance() + { AddStep("add new score", () => { var ev = new NewScoreEvent(1, new APIUser @@ -67,5 +73,24 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("set user score", () => breakdown.UserBestScore.Value = new MultiplayerScore { TotalScore = RNG.Next(1_000_000) }); AddStep("unset user score", () => breakdown.UserBestScore.Value = null); } + + [Test] + public void TestMassAdd() + { + AddStep("add 1000 scores at once", () => + { + for (int i = 0; i < 1000; i++) + { + var ev = new NewScoreEvent(1, new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, RNG.Next(1_000_000), null); + + breakdown.AddNewScore(ev); + } + }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs index b35379e126..71ab73b535 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeScoreBreakdown.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,18 +68,39 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }); } + private readonly Queue newScores = new Queue(); + public void AddNewScore(NewScoreEvent newScoreEvent) { - int targetBin = (int)Math.Clamp(Math.Floor((float)newScoreEvent.TotalScore / 100000), 0, bin_count - 1); - bins[targetBin] += 1; + newScores.Enqueue(newScoreEvent); - Scheduler.AddOnce(updateCounts); - - if (Alpha > 0) + // ensure things don't get too out-of-hand. + if (newScores.Count > 25) { + bins[getTargetBin(newScores.Dequeue())] += 1; + Scheduler.AddOnce(updateCounts); + } + } + + private double lastScoreDisplay; + + protected override void Update() + { + base.Update(); + + if (Time.Current - lastScoreDisplay > 150 && newScores.TryDequeue(out var newScore)) + { + if (lastScoreDisplay < Time.Current) + lastScoreDisplay = Time.Current; + + int targetBin = getTargetBin(newScore); + bins[targetBin] += 1; + + updateCounts(); + var text = new OsuSpriteText { - Text = newScoreEvent.TotalScore.ToString(@"N0"), + Text = newScore.TotalScore.ToString(@"N0"), Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, Font = OsuFont.Default.With(size: 30), @@ -98,6 +120,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge .FadeOut(2500, Easing.OutQuint) .Expire(); }, 150); + + lastScoreDisplay = Time.Current; } } @@ -110,6 +134,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge updateCounts(); } + private static int getTargetBin(NewScoreEvent score) => + (int)Math.Clamp(Math.Floor((float)score.TotalScore / 100000), 0, bin_count - 1); + private void updateCounts() { long max = Math.Max(bins.Max(), 1); From b46f3c97da76f767c455e42dc73a16a0ca73e400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 14:20:47 +0200 Subject: [PATCH 405/670] Add notification on daily challenge conclusion & start of new one Because I wish to stop seeing "DAILY CHALLENGE WHERE" every day on #general. The notifications are constrained to the daily challenge screen only to not spam users who may not care. --- .../DailyChallenge/TestSceneDailyChallenge.cs | 73 +++++++++++++++++++ .../DailyChallenge/DailyChallenge.cs | 33 +++++++++ .../NewDailyChallengeNotification.cs | 44 +++++++++++ .../Visual/Metadata/TestMetadataClient.cs | 2 +- 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index cd09a1d20f..4a5f452ed1 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -4,16 +4,32 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Online.API; +using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Metadata; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.DailyChallenge { public partial class TestSceneDailyChallenge : OnlinePlayTestScene { + [Cached(typeof(MetadataClient))] + private TestMetadataClient metadataClient = new TestMetadataClient(); + + [Cached(typeof(INotificationOverlay))] + private NotificationOverlay notificationOverlay = new NotificationOverlay(); + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(notificationOverlay); + } + [Test] public void TestDailyChallenge() { @@ -36,5 +52,62 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); } + + [Test] + public void TestNotifications() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); + AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + AddStep("install custom handler", () => + { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetRoomRequest r: + { + r.TriggerSuccess(new Room + { + RoomID = { Value = 1235, }, + Name = { Value = "Daily Challenge: June 5, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }); + return true; + } + + default: + return false; + } + }; + }); + AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 4b4e4a7a62..d176f36162 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -30,6 +30,7 @@ using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; @@ -54,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private readonly Bindable> userMods = new Bindable>(Array.Empty()); private readonly IBindable apiState = new Bindable(); + private readonly IBindable dailyChallengeInfo = new Bindable(); private OnlinePlayScreenWaveContainer waves = null!; private DailyChallengeLeaderboard leaderboard = null!; @@ -65,6 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; + private SimpleNotification? waitForNextChallengeNotification; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -98,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private PreviewTrackManager previewTrackManager { get; set; } = null!; + [Resolved] + private INotificationOverlay? notificationOverlay { get; set; } + public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool? ApplyModTrackAdjustments => true; @@ -336,6 +343,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; + dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo); ((IBindable)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore); } @@ -388,6 +396,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge apiState.BindTo(API.State); apiState.BindValueChanged(onlineStateChanged, true); + + dailyChallengeInfo.BindValueChanged(dailyChallengeChanged); } private void trySetDailyChallengeBeatmap() @@ -405,6 +415,29 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Schedule(forcefullyExit); }); + private void dailyChallengeChanged(ValueChangedEvent change) + { + if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) + { + notificationOverlay?.Post(waitForNextChallengeNotification = new SimpleNotification + { + Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." + }); + } + + if (change.NewValue != null && change.NewValue.Value.RoomID != room.RoomID.Value) + { + var roomRequest = new GetRoomRequest(change.NewValue.Value.RoomID); + + roomRequest.Success += room => + { + waitForNextChallengeNotification?.Close(false); + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + }; + API.Queue(roomRequest); + } + } + private void forcefullyExit() { Logger.Log($"{this} forcefully exiting due to loss of API connection"); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs new file mode 100644 index 0000000000..36ec8b37a7 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -0,0 +1,44 @@ +// 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.Screens; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Overlays.Notifications; +using osu.Game.Screens.Menu; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class NewDailyChallengeNotification : SimpleNotification + { + private readonly Room room; + + private BeatmapCardNano card = null!; + + public NewDailyChallengeNotification(Room room) + { + this.room = room; + } + + [BackgroundDependencyLoader] + private void load(OsuGame? game) + { + Text = "Today's daily challenge is here! Click here to play."; + Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); + Activated = () => + { + game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]); + return true; + }; + } + + protected override void Update() + { + base.Update(); + card.Width = Content.DrawWidth; + } + } +} diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index fa64a83352..2a0af0b10e 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Metadata public override IBindableDictionary UserStates => userStates; private readonly BindableDictionary userStates = new BindableDictionary(); - public override IBindable DailyChallengeInfo => dailyChallengeInfo; + public override Bindable DailyChallengeInfo => dailyChallengeInfo; private readonly Bindable dailyChallengeInfo = new Bindable(); [Resolved] From 1daeb7ebd0cb673c6dd63158c5fe70856ff02f61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jul 2024 22:19:38 +0900 Subject: [PATCH 406/670] Rename typo in test naming --- osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index bb73a458a3..7b7565b13f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestBackgroundAltering() + public void TestBackgroundAlternating() { var localUser = new APIUser { From b77a10b6db27757ee6e8bdd09a5eebccd7557fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Jul 2024 15:28:52 +0200 Subject: [PATCH 407/670] Fix tests maybe --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 4a5f452ed1..b6dcc82ac1 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -76,8 +76,12 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + + Func? previousHandler = null; + AddStep("install custom handler", () => { + previousHandler = ((DummyAPIAccess)API).HandleRequest; ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -108,6 +112,8 @@ namespace osu.Game.Tests.Visual.DailyChallenge }; }); AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); + + AddStep("restore previous handler", () => ((DummyAPIAccess)API).HandleRequest = previousHandler); } } } From 621f4dfece39d1323fc209c48293289c992d5b3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 02:45:12 +0300 Subject: [PATCH 408/670] Enforce new line between X/Y coordinate in editor position inspector --- osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs index 2f19888e9e..de23147e7b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs @@ -58,7 +58,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { case IHasPosition pos: AddHeader("Position"); - AddValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}"); + AddValue($"x:{pos.X:#,0.##}"); + AddValue($"y:{pos.Y:#,0.##}"); break; case IHasXPosition x: From 78417db06d1053cae9288db7afd5a46afa8f5659 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:35:09 +0300 Subject: [PATCH 409/670] Remove stray line --- .../Profile/Header/Components/DailyChallengeStreakTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 02be0f2c99..5f28928665 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -203,7 +203,6 @@ namespace osu.Game.Overlays.Profile.Header.Components }, valueText = new OsuSpriteText { - // Colour = colour Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), } }; From 9868fb4aaa736c6956b549f0317a87e328961dc8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:36:02 +0300 Subject: [PATCH 410/670] Remove tier-based colour from the condensed piece to match web --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index 87f833d165..b0f57099f5 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -102,7 +102,6 @@ namespace osu.Game.Overlays.Profile.Header.Components var statistics = User.Value.User.DailyChallengeStatistics; // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; - dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); } From 8b910e59f68ec871ae3f7f1d7543c14c906ab32d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 06:37:17 +0300 Subject: [PATCH 411/670] Reduce tooltip shadow outline --- .../Profile/Header/Components/DailyChallengeStreakTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index 5f28928665..a105659ac7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Profile.Header.Components EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.25f), Radius = 30f, }; From 33fc6dfaffe694e78ef4fc23c3871ba16eb286a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:05:31 +0300 Subject: [PATCH 412/670] Hide daily challenge display when not selecting osu! Also hide when no user is displayed. --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index b0f57099f5..a4c62d4357 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -93,9 +93,9 @@ namespace osu.Game.Overlays.Profile.Header.Components private void updateDisplay() { - if (User.Value == null) + if (User.Value == null || User.Value.Ruleset.OnlineID != 0) { - dailyStreak.Text = "-"; + Hide(); return; } @@ -103,6 +103,7 @@ namespace osu.Game.Overlays.Profile.Header.Components // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); + Show(); } public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); From 7c3d592a84451a9c2b2ee5a853e43035ed15d458 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:04:52 +0300 Subject: [PATCH 413/670] Fix user profile overlay test scene being broke --- .../Visual/Online/TestSceneUserProfileOverlay.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 937e08cb97..3bb38f167f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API; @@ -24,7 +25,17 @@ namespace osu.Game.Tests.Visual.Online [SetUpSteps] public void SetUp() { - AddStep("create profile overlay", () => Child = profile = new UserProfileOverlay()); + AddStep("create profile overlay", () => + { + profile = new UserProfileOverlay(); + + Child = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(UserProfileOverlay), profile) }, + Child = profile, + }; + }); } [Test] @@ -131,6 +142,7 @@ namespace osu.Game.Tests.Visual.Online CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue, + PlayMode = "osu", }); return true; } @@ -174,6 +186,7 @@ namespace osu.Game.Tests.Visual.Online CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue, + PlayMode = "osu", })); int hue2 = 0; @@ -189,6 +202,7 @@ namespace osu.Game.Tests.Visual.Online CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue2, + PlayMode = "osu", })); } From dca61eb76caca6b063025d39ba4c63d3865d25f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 07:07:10 +0300 Subject: [PATCH 414/670] Remove no longer used dependency --- .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index a4c62d4357..a9d0ab4f01 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -24,9 +24,6 @@ namespace osu.Game.Overlays.Profile.Header.Components [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [BackgroundDependencyLoader] private void load() { From 91dfe4515bbd74d1b0260788c2c1c12517842428 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 30 Jul 2024 08:12:03 +0300 Subject: [PATCH 415/670] Fix daily challenge display showing incorrect statistic --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 1 + .../Profile/Header/Components/DailyChallengeStreakDisplay.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index e2d26f222c..c0fb7b49f0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Online AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); + AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index a9d0ab4f01..da0e334a4e 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -97,8 +97,8 @@ namespace osu.Game.Overlays.Profile.Header.Components } var statistics = User.Value.User.DailyChallengeStatistics; - // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); - dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; + // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount); + dailyStreak.Text = $"{statistics.PlayCount}d"; TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); Show(); } From ae38e66036b62ceb9b3a2b2c8687878ffd5ca710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 08:17:23 +0200 Subject: [PATCH 416/670] Add failing test coverage --- .../Mods/ModDifficultyAdjustTest.cs | 47 +++++++++++++++++-- .../TestSceneModDifficultyAdjustSettings.cs | 29 +++++++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index 4101652c49..e31a3dbdf0 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -8,6 +8,8 @@ using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Tests.Mods @@ -105,9 +107,6 @@ namespace osu.Game.Tests.Mods testMod.ResetSettingsToDefaults(); Assert.That(testMod.DrainRate.Value, Is.Null); - - // ReSharper disable once HeuristicUnreachableCode - // see https://youtrack.jetbrains.com/issue/RIDER-70159. Assert.That(testMod.OverallDifficulty.Value, Is.Null); var applied = applyDifficulty(new BeatmapDifficulty @@ -119,6 +118,48 @@ namespace osu.Game.Tests.Mods Assert.That(applied.OverallDifficulty, Is.EqualTo(10)); } + [Test] + public void TestDeserializeIncorrectRange() + { + var apiMod = new APIMod + { + Acronym = @"DA", + Settings = new Dictionary + { + [@"circle_size"] = -727, + [@"approach_rate"] = -727, + } + }; + var ruleset = new OsuRuleset(); + + var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset); + + Assert.Multiple(() => + { + Assert.That(mod.CircleSize.Value, Is.GreaterThanOrEqualTo(0).And.LessThanOrEqualTo(11)); + Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11)); + }); + } + + [Test] + public void TestDeserializeNegativeApproachRate() + { + var apiMod = new APIMod + { + Acronym = @"DA", + Settings = new Dictionary + { + [@"approach_rate"] = -9, + } + }; + var ruleset = new OsuRuleset(); + + var mod = (OsuModDifficultyAdjust)apiMod.ToMod(ruleset); + + Assert.That(mod.ApproachRate.Value, Is.GreaterThanOrEqualTo(-10).And.LessThanOrEqualTo(11)); + Assert.That(mod.ApproachRate.Value, Is.EqualTo(-9)); + } + /// /// Applies a to the mod and returns a new /// representing the result if the mod were applied to a fresh instance. diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 307f436f84..b40d0b10d2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestOutOfRangeValueStillApplied() + public void TestValueAboveRangeStillApplied() { AddStep("set override cs to 11", () => modDifficultyAdjust.CircleSize.Value = 11); @@ -91,6 +91,28 @@ namespace osu.Game.Tests.Visual.UserInterface checkBindableAtValue("Circle Size", 11); } + [Test] + public void TestValueBelowRangeStillApplied() + { + AddStep("set override cs to -5", () => modDifficultyAdjust.ApproachRate.Value = -5); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + + // this is a no-op, just showing that it won't reset the value during deserialisation. + setExtendedLimits(false); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + + // setting extended limits will reset the serialisation exception. + // this should be fine as the goal is to allow, at most, the value of extended limits. + setExtendedLimits(true); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + } + [Test] public void TestExtendedLimits() { @@ -109,6 +131,11 @@ namespace osu.Game.Tests.Visual.UserInterface checkSliderAtValue("Circle Size", 11); checkBindableAtValue("Circle Size", 11); + setSliderValue("Approach Rate", -5); + + checkSliderAtValue("Approach Rate", -5); + checkBindableAtValue("Approach Rate", -5); + setExtendedLimits(false); checkSliderAtValue("Circle Size", 10); From 6813f5ee0a746f4f6782de75c8fc7d46ac16d4e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 08:17:35 +0200 Subject: [PATCH 417/670] Fix incorrect `DifficultyBindable` logic --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index 5f6fd21860..099806d320 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mods public float MinValue { + get => minValue; set { if (value == minValue) @@ -52,6 +53,7 @@ namespace osu.Game.Rulesets.Mods public float MaxValue { + get => maxValue; set { if (value == maxValue) @@ -69,6 +71,7 @@ namespace osu.Game.Rulesets.Mods /// public float? ExtendedMinValue { + get => extendedMinValue; set { if (value == extendedMinValue) @@ -86,6 +89,7 @@ namespace osu.Game.Rulesets.Mods /// public float? ExtendedMaxValue { + get => extendedMaxValue; set { if (value == extendedMaxValue) @@ -114,9 +118,14 @@ namespace osu.Game.Rulesets.Mods { // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. if (value != null) - CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value); + { + CurrentNumber.MinValue = Math.Clamp(MathF.Min(CurrentNumber.MinValue, value.Value), ExtendedMinValue ?? MinValue, MinValue); + CurrentNumber.MaxValue = Math.Clamp(MathF.Max(CurrentNumber.MaxValue, value.Value), MaxValue, ExtendedMaxValue ?? MaxValue); - base.Value = value; + base.Value = Math.Clamp(value.Value, CurrentNumber.MinValue, CurrentNumber.MaxValue); + } + else + base.Value = value; } } @@ -138,6 +147,8 @@ namespace osu.Game.Rulesets.Mods // the following max value copies are only safe as long as these values are effectively constants. otherDifficultyBindable.MaxValue = maxValue; otherDifficultyBindable.ExtendedMaxValue = extendedMaxValue; + otherDifficultyBindable.MinValue = minValue; + otherDifficultyBindable.ExtendedMinValue = extendedMinValue; } public override void BindTo(Bindable them) From bf10a910826fa68b65e1eb4f0e22b5a77003bad5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 15:13:07 +0900 Subject: [PATCH 418/670] Adjust colouring to work better across multiple usages --- osu.Game/Overlays/Chat/ChatLine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 87e1f5699b..e4564724f0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -123,13 +123,13 @@ namespace osu.Game.Overlays.Chat background = new Container { Masking = true, - Blending = BlendingParameters.Additive, CornerRadius = 4, Alpha = 0, RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, Child = new Box { - Colour = Color4.White, + Colour = Colour4.FromHex("#3b3234"), RelativeSizeAxes = Axes.Both, }, }, @@ -301,7 +301,7 @@ namespace osu.Game.Overlays.Chat private void updateBackground() { if (background != null) - background.Alpha = alternatingBackground ? 0.03f : 0; + background.Alpha = alternatingBackground ? 0.2f : 0; } } } From fc78dc9f3890bd068b4e5d62202467e45476a15c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:02:49 +0900 Subject: [PATCH 419/670] Adjust paddings to avoid scrollbar overlap --- osu.Game/Overlays/Chat/ChatLine.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e4564724f0..a233c18115 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -118,6 +118,8 @@ namespace osu.Game.Overlays.Chat configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime); prefer24HourTime.BindValueChanged(_ => updateTimestamp()); + Padding = new MarginPadding { Right = 5 }; + InternalChildren = new[] { background = new Container @@ -135,10 +137,10 @@ namespace osu.Game.Overlays.Chat }, new GridContainer { - Margin = new MarginPadding + Padding = new MarginPadding { - Horizontal = 10, - Vertical = 1, + Horizontal = 2, + Vertical = 2, }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From a05f8107247bc6896a8716a22139b6143f332621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 10:07:38 +0200 Subject: [PATCH 420/670] Attempt to fix tests more --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index b6dcc82ac1..b4d0b746a7 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; @@ -74,7 +75,10 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); - AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + + Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; + AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); Func? previousHandler = null; From 1b57a2a136c626939669a1a6f6e425b93f823ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jul 2024 10:36:26 +0200 Subject: [PATCH 421/670] Show new daily challenge notification globally --- .../DailyChallenge/TestSceneDailyChallenge.cs | 38 ---------------- .../UserInterface/TestSceneMainMenuButton.cs | 44 +++++++++++++++---- osu.Game/Screens/Menu/DailyChallengeButton.cs | 16 +++++-- .../DailyChallenge/DailyChallenge.cs | 12 ----- 4 files changed, 48 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index b4d0b746a7..25b3375f9e 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -80,44 +80,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); - - Func? previousHandler = null; - - AddStep("install custom handler", () => - { - previousHandler = ((DummyAPIAccess)API).HandleRequest; - ((DummyAPIAccess)API).HandleRequest = req => - { - switch (req) - { - case GetRoomRequest r: - { - r.TriggerSuccess(new Room - { - RoomID = { Value = 1235, }, - Name = { Value = "Daily Challenge: June 5, 2024" }, - Playlist = - { - new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) - { - RequiredMods = [new APIMod(new OsuModTraceable())], - AllowedMods = [new APIMod(new OsuModDoubleTime())] - } - }, - EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, - Category = { Value = RoomCategory.DailyChallenge } - }); - return true; - } - - default: - return false; - } - }; - }); - AddStep("next daily challenge started", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1235 }); - - AddStep("restore previous handler", () => ((DummyAPIAccess)API).HandleRequest = previousHandler); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index 5914898cb1..af98aa21db 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,6 +11,7 @@ using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; using Color4 = osuTK.Graphics.Color4; @@ -39,8 +41,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDailyChallengeButton() { - AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); - AddStep("set up API", () => dummyAPI.HandleRequest = req => { switch (req) @@ -67,17 +67,45 @@ namespace osu.Game.Tests.Visual.UserInterface } }); - AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ButtonSystemState = ButtonSystemState.TopLevel, - }); + NotificationOverlay notificationOverlay = null!; + DependencyProvidingContainer buttonContainer = null!; AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = 1234, })); + AddStep("add content", () => + { + notificationOverlay = new NotificationOverlay(); + Children = new Drawable[] + { + notificationOverlay, + buttonContainer = new DependencyProvidingContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)], + Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + ButtonSystemState = ButtonSystemState.TopLevel, + }, + }, + }; + }); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + + AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + + AddStep("hide button's parent", () => buttonContainer.Hide()); + AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234, + })); + AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); } } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index c365994736..a5616b95a0 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -23,6 +23,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Metadata; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.DailyChallenge; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -44,6 +45,9 @@ namespace osu.Game.Screens.Menu [Resolved] private IAPIProvider api { get; set; } = null!; + [Resolved] + private INotificationOverlay? notificationOverlay { get; set; } + public DailyChallengeButton(string sampleName, Color4 colour, Action? clickAction = null, params Key[] triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) { @@ -100,7 +104,8 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - info.BindValueChanged(updateDisplay, true); + info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true)); + dailyChallengeChanged(postNotification: false); } protected override void Update() @@ -126,27 +131,30 @@ namespace osu.Game.Screens.Menu } } - private void updateDisplay(ValueChangedEvent info) + private void dailyChallengeChanged(bool postNotification) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (info.NewValue == null) + if (info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID); + var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); roomRequest.Success += room => { Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; + if (postNotification) + notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + updateCountdown(); Scheduler.AddDelayed(updateCountdown, 1000, true); }; diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index d176f36162..32209bc3b4 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -424,18 +424,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." }); } - - if (change.NewValue != null && change.NewValue.Value.RoomID != room.RoomID.Value) - { - var roomRequest = new GetRoomRequest(change.NewValue.Value.RoomID); - - roomRequest.Success += room => - { - waitForNextChallengeNotification?.Close(false); - notificationOverlay?.Post(new NewDailyChallengeNotification(room)); - }; - API.Queue(roomRequest); - } } private void forcefullyExit() From e63080eb2e5932ac97a656a0cfb2c06c7b3f96f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:59:46 +0900 Subject: [PATCH 422/670] Don't show seconds in chat timestamps --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index a233c18115..6538bcfcf4 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -258,7 +258,7 @@ namespace osu.Game.Overlays.Chat private void updateTimestamp() { - drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm:ss" : @"hh:mm:ss tt"); + drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm" : @"hh:mm tt"); } private static readonly Color4[] default_username_colours = From a2a73232f3bdf6117e2d17bd3ae41423a32cc61b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 16:59:32 +0900 Subject: [PATCH 423/670] Avoid showing timestamp in chat line when repeated --- osu.Game/Overlays/Chat/ChatLine.cs | 31 +++++++++++++++++++++-- osu.Game/Overlays/Chat/DrawableChannel.cs | 6 +++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 6538bcfcf4..4d228b2af0 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -71,6 +71,25 @@ namespace osu.Game.Overlays.Chat private Drawable? background; private bool alternatingBackground; + private bool requiresTimestamp = true; + + + public bool RequiresTimestamp + { + get => requiresTimestamp; + set + { + if (requiresTimestamp == value) + return; + + requiresTimestamp = value; + + if (!IsLoaded) + return; + + updateMessageContent(); + } + } public bool AlternatingBackground { @@ -244,9 +263,17 @@ namespace osu.Game.Overlays.Chat private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); - drawableTimestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); - updateTimestamp(); + if (requiresTimestamp && !(message is LocalEchoMessage)) + { + drawableTimestamp.Show(); + updateTimestamp(); + } + else + { + drawableTimestamp.Hide(); + } + drawableUsername.Text = $@"{message.Sender.Username}"; // remove non-existent channels from the link list diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6b3acaa226..05d09401a9 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,10 +88,16 @@ namespace osu.Game.Overlays.Chat { base.Update(); + int? minute = null; + for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) + { chatline.AlternatingBackground = i % 2 == 0; + chatline.RequiresTimestamp = chatline.Message.Timestamp.Minute != minute; + minute = chatline.Message.Timestamp.Minute; + } } } From 71649005bf02f5a158afdabe9d6ffd9273690777 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:00:12 +0900 Subject: [PATCH 424/670] Elongate usernames in `DrawableChannel` test --- .../Visual/Online/TestSceneDrawableChannel.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index 7b7565b13f..dd12ee34ed 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; @@ -88,19 +89,17 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestBackgroundAlternating() { - var localUser = new APIUser - { - Id = 3, - Username = "LocalUser" - }; - int messageCount = 1; AddRepeatStep("add messages", () => { channel.AddNewMessages(new Message(messageCount) { - Sender = localUser, + Sender = new APIUser + { + Id = 3, + Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N") + }, Content = "Hi there all!", Timestamp = new DateTimeOffset(2022, 11, 21, 20, messageCount, 13, TimeSpan.Zero), Uuid = Guid.NewGuid().ToString(), From 4557ad43d5622221471a31d119bb4319d202cea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:00:26 +0900 Subject: [PATCH 425/670] Reduce padding on chat lines to give more breathing room --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 05d09401a9..d20506ea4c 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Horizontal = 10 }, + Padding = new MarginPadding { Left = 3, Right = 10 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, From 6670f79258dc6fb9ab25505fc38d92df1158f562 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:01:16 +0900 Subject: [PATCH 426/670] Reduce overall size of chat text --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 4 ++-- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- osu.Game/Overlays/Chat/DaySeparator.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index e3b5037367..440486d6a0 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -178,7 +178,7 @@ namespace osu.Game.Online.Chat protected partial class StandAloneDaySeparator : DaySeparator { - protected override float TextSize => 14; + protected override float TextSize => 13; protected override float LineHeight => 1; protected override float Spacing => 5; protected override float DateAlign => 125; @@ -198,7 +198,7 @@ namespace osu.Game.Online.Chat protected partial class StandAloneMessage : ChatLine { - protected override float FontSize => 15; + protected override float FontSize => 13; protected override float Spacing => 5; protected override float UsernameWidth => 75; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 4d228b2af0..adb193af32 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat public IReadOnlyCollection DrawableContentFlow => drawableContentFlow; - protected virtual float FontSize => 14; + protected virtual float FontSize => 12; protected virtual float Spacing => 15; diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs index e737b787ba..fd6b15c778 100644 --- a/osu.Game/Overlays/Chat/DaySeparator.cs +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat { public partial class DaySeparator : Container { - protected virtual float TextSize => 15; + protected virtual float TextSize => 13; protected virtual float LineHeight => 2; From 7229ae83ea8449be6bd1bf290c69b3cb7bf2e494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 17:16:27 +0900 Subject: [PATCH 427/670] Adjust sizing and distribution of timestamp and username --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 440486d6a0..3a094cc074 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -200,7 +200,7 @@ namespace osu.Game.Online.Chat { protected override float FontSize => 13; protected override float Spacing => 5; - protected override float UsernameWidth => 75; + protected override float UsernameWidth => 90; public StandAloneMessage(Message message) : base(message) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index adb193af32..29c6ec2564 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Chat @@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Chat protected virtual float Spacing => 15; - protected virtual float UsernameWidth => 130; + protected virtual float UsernameWidth => 150; [Resolved] private ChannelManager? chatManager { get; set; } @@ -73,7 +74,6 @@ namespace osu.Game.Overlays.Chat private bool alternatingBackground; private bool requiresTimestamp = true; - public bool RequiresTimestamp { get => requiresTimestamp; @@ -166,7 +166,7 @@ namespace osu.Game.Overlays.Chat RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, ColumnDimensions = new[] { - new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 45), new Dimension(GridSizeMode.Absolute, Spacing + UsernameWidth + Spacing), new Dimension(), }, @@ -177,9 +177,10 @@ namespace osu.Game.Overlays.Chat drawableTimestamp = new OsuSpriteText { Shadow = false, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true), + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Spacing = new Vector2(-1, 0), + Font = OsuFont.GetFont(size: FontSize, weight: FontWeight.SemiBold, fixedWidth: true), AlwaysPresent = true, }, drawableUsername = new DrawableChatUsername(message.Sender) From 25747fdeb3c3a57db92fda84de3189f48c6784b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 18:06:56 +0900 Subject: [PATCH 428/670] Fix edge case where minutes are same but hour is different --- osu.Game/Overlays/Chat/DrawableChannel.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d20506ea4c..97660a34f1 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,15 +88,17 @@ namespace osu.Game.Overlays.Chat { base.Update(); - int? minute = null; + int? lastMinutes = null; for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { + int minutes = chatline.Message.Timestamp.TotalOffsetMinutes; + chatline.AlternatingBackground = i % 2 == 0; - chatline.RequiresTimestamp = chatline.Message.Timestamp.Minute != minute; - minute = chatline.Message.Timestamp.Minute; + chatline.RequiresTimestamp = minutes != lastMinutes; + lastMinutes = minutes; } } } From d5f9173288ecd3a4030bd07e47b82e5c897f3af5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 19:04:43 +0900 Subject: [PATCH 429/670] Remove unused local variable --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 32209bc3b4..e98e758ceb 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -67,8 +67,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private DailyChallengeTotalsDisplay totals = null!; private DailyChallengeEventFeed feed = null!; - private SimpleNotification? waitForNextChallengeNotification; - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -419,7 +417,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) { - notificationOverlay?.Post(waitForNextChallengeNotification = new SimpleNotification + notificationOverlay?.Post(new SimpleNotification { Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." }); From ff7815c3c563b2ad607d47da76ccbb9b1f0cc24b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Jul 2024 20:13:00 +0900 Subject: [PATCH 430/670] Submit vertices in local space to avoid cross-thread access --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 49e4ee18c1..5e8061bb6a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -220,7 +220,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private float fadeExponent; private readonly TrailPart[] parts = new TrailPart[max_sprites]; - private Vector2 size; private Vector2 originPosition; private IVertexBatch vertexBatch; @@ -236,7 +235,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor shader = Source.shader; texture = Source.texture; - size = Source.partSize; time = Source.time; fadeExponent = Source.FadeExponent; @@ -277,6 +275,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RectangleF textureRect = texture.GetTextureRect(); + renderer.PushLocalMatrix(DrawInfo.Matrix); + foreach (var part in parts) { if (part.InvalidationID == -1) @@ -285,11 +285,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (time - part.Time >= 1) continue; - Vector2 screenSpacePos = Source.ToScreenSpace(part.Position); - vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -298,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y + size.Y * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -307,7 +305,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X + size.X * (1 - originPosition.X), screenSpacePos.Y - size.Y * originPosition.Y), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -316,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(screenSpacePos.X - size.X * originPosition.X, screenSpacePos.Y - size.Y * originPosition.Y), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, @@ -324,6 +322,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }); } + renderer.PopLocalMatrix(); + vertexBatch.Draw(); shader.Unbind(); } From 7f22ade90da1426924b220f76373f2dcc5414911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jul 2024 21:58:54 +0900 Subject: [PATCH 431/670] Fix oversight in timekeeping --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 97660a34f1..41098ef823 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -88,13 +88,13 @@ namespace osu.Game.Overlays.Chat { base.Update(); - int? lastMinutes = null; + long? lastMinutes = null; for (int i = 0; i < ChatLineFlow.Count; i++) { if (ChatLineFlow[i] is ChatLine chatline) { - int minutes = chatline.Message.Timestamp.TotalOffsetMinutes; + long minutes = chatline.Message.Timestamp.ToUnixTimeSeconds() / 60; chatline.AlternatingBackground = i % 2 == 0; chatline.RequiresTimestamp = minutes != lastMinutes; From 5ebb5ad6707f1ca29c18ced13683c5ecfacedcb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 02:53:10 +0900 Subject: [PATCH 432/670] Fix test failure due to `TestMetadataClient` providing null statistics array --- .../Visual/DailyChallenge/TestSceneDailyChallenge.cs | 3 ++- osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs index 25b3375f9e..e10b3f76e6 100644 --- a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs @@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge private void load() { base.Content.Add(notificationOverlay); + base.Content.Add(metadataClient); } [Test] @@ -63,7 +64,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge Name = { Value = "Daily Challenge: June 4, 2024" }, Playlist = { - new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) { RequiredMods = [new APIMod(new OsuModTraceable())], AllowedMods = [new APIMod(new OsuModDoubleTime())] diff --git a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs index 2a0af0b10e..c9f2b183e3 100644 --- a/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs +++ b/osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs @@ -88,7 +88,14 @@ namespace osu.Game.Tests.Visual.Metadata } public override Task BeginWatchingMultiplayerRoom(long id) - => Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]); + { + var stats = new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]; + + for (int i = 0; i < stats.Length; i++) + stats[i] = new MultiplayerPlaylistItemStats { PlaylistItemID = i }; + + return Task.FromResult(stats); + } public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask; } From bdc465e1c68c29841ff01dc8efc07817d92f1a51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 03:06:35 +0900 Subject: [PATCH 433/670] Reword notification text slightly --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 +- .../OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index db85db2cd3..8c8b6bdbf0 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -419,7 +419,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { notificationOverlay?.Post(new SimpleNotification { - Text = "Today's daily challenge has concluded. Thanks for playing! The next one should appear in a few minutes." + Text = "Today's daily challenge has concluded – thanks for playing!\n\nTomorrow's challenge is now being prepared and will appear soon." }); } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 36ec8b37a7..3f14e63a2d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [BackgroundDependencyLoader] private void load(OsuGame? game) { - Text = "Today's daily challenge is here! Click here to play."; + Text = "Today's daily challenge is now live! Click here to play."; Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { From e77489f2a9d8e6edd6d75e888512e34064e3644f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 03:10:36 +0900 Subject: [PATCH 434/670] Allow notification of new strings --- .../Localisation/DailyChallengeStrings.cs | 29 +++++++++++++++++++ .../DailyChallenge/DailyChallenge.cs | 7 ++--- .../NewDailyChallengeNotification.cs | 3 +- 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Localisation/DailyChallengeStrings.cs diff --git a/osu.Game/Localisation/DailyChallengeStrings.cs b/osu.Game/Localisation/DailyChallengeStrings.cs new file mode 100644 index 0000000000..32ff98db06 --- /dev/null +++ b/osu.Game/Localisation/DailyChallengeStrings.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class DailyChallengeStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DailyChallenge"; + + /// + /// "Today's daily challenge has concluded – thanks for playing! + /// + /// Tomorrow's challenge is now being prepared and will appear soon." + /// + public static LocalisableString ChallengeEndedNotification => new TranslatableString(getKey(@"todays_daily_challenge_has_concluded"), + @"Today's daily challenge has concluded – thanks for playing! + +Tomorrow's challenge is now being prepared and will appear soon."); + + /// + /// "Today's daily challenge is now live! Click here to play." + /// + public static LocalisableString ChallengeLiveNotification => new TranslatableString(getKey(@"todays_daily_challenge_is_now"), @"Today's daily challenge is now live! Click here to play."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 8c8b6bdbf0..da2d9036c5 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -417,16 +417,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null) { - notificationOverlay?.Post(new SimpleNotification - { - Text = "Today's daily challenge has concluded – thanks for playing!\n\nTomorrow's challenge is now being prepared and will appear soon." - }); + notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification }); } } private void forcefullyExit() { - Logger.Log($"{this} forcefully exiting due to loss of API connection"); + Logger.Log(@$"{this} forcefully exiting due to loss of API connection"); // This is temporary since we don't currently have a way to force screens to be exited // See also: `OnlinePlayScreen.forcefullyExit()` diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs index 3f14e63a2d..ea19828a21 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/NewDailyChallengeNotification.cs @@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays.Notifications; using osu.Game.Screens.Menu; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { @@ -26,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [BackgroundDependencyLoader] private void load(OsuGame? game) { - Text = "Today's daily challenge is now live! Click here to play."; + Text = DailyChallengeStrings.ChallengeLiveNotification; Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!)); Activated = () => { From cbfb569ad47d17a6de63dd6484d3c9a15cd2d452 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 14:37:56 +0900 Subject: [PATCH 435/670] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7d9d4c022..3d8b643279 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 9e03dc3b5e8b67f6e8ff27ecbd213c2c15118244 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Wed, 31 Jul 2024 16:52:53 +0800 Subject: [PATCH 436/670] Implement maximum width on `CommentTooltip` --- .../Overlays/Comments/CommentAuthorLine.cs | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index 1f6fef4df3..6c04f332bb 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -1,15 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -139,11 +142,13 @@ namespace osu.Game.Overlays.Comments } } - private partial class ParentUsername : FillFlowContainer, IHasTooltip + private partial class ParentUsername : FillFlowContainer, IHasCustomTooltip { - public LocalisableString TooltipText => getParentMessage(); + public ITooltip GetCustomTooltip() => new CommentTooltip(); - private readonly Comment? parentComment; + LocalisableString IHasCustomTooltip.TooltipContent => getParentMessage(); + + private Comment? parentComment { get; } public ParentUsername(Comment comment) { @@ -176,5 +181,60 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? CommentsStrings.Deleted : string.Empty; } } + + private partial class CommentTooltip : VisibilityContainer, ITooltip + { + private const int max_width = 500; + + private TextFlowContainer content { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + Masking = true; + CornerRadius = 7; + + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both + }, + content = new TextFlowContainer(f => + { + f.Font = OsuFont.Default; + f.Truncate = true; + f.MaxWidth = max_width; + }) + { + Margin = new MarginPadding(3), + AutoSizeAxes = Axes.Both, + MaximumSize = new Vector2(max_width, float.PositiveInfinity), + } + }; + + FinishTransforms(); + } + + private LocalisableString lastPresent; + + public void SetContent(LocalisableString content) + { + if (lastPresent.Equals(content)) + return; + + this.content.Text = content; + lastPresent = content; + } + + public void Move(Vector2 pos) => Position = pos; + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } } } From 5b46597d562ef2526d9f8206ae98af47a5919370 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Wed, 31 Jul 2024 16:54:32 +0800 Subject: [PATCH 437/670] fix click to expand on touch devices --- .../Overlays/Mods/ModCustomisationHeader.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 5a9e6099e6..3d6a23043d 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -95,11 +95,30 @@ namespace osu.Game.Overlays.Mods }, true); } + private bool touchedThisFrame; + + protected override bool OnTouchDown(TouchDownEvent e) + { + if (Enabled.Value) + { + touchedThisFrame = true; + Schedule(() => touchedThisFrame = false); + } + + return base.OnTouchDown(e); + } + protected override bool OnHover(HoverEvent e) { if (Enabled.Value) { - Parent?.UpdateHoverExpansion(true); + if (!touchedThisFrame) + Parent?.UpdateHoverExpansion(true); + } + if (Enabled.Value) + { + if (!touchedThisFrame) + Parent?.UpdateHoverExpansion(true); } return base.OnHover(e); From 5fb364cad6dff7243d03fe8287c4a79590f80104 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Wed, 31 Jul 2024 16:56:25 +0800 Subject: [PATCH 438/670] remove redundant code added accidentally --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 3d6a23043d..306675a741 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -115,11 +115,6 @@ namespace osu.Game.Overlays.Mods if (!touchedThisFrame) Parent?.UpdateHoverExpansion(true); } - if (Enabled.Value) - { - if (!touchedThisFrame) - Parent?.UpdateHoverExpansion(true); - } return base.OnHover(e); } From 04ecefe2268a89d7471f207668bcb1aff725db83 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Wed, 31 Jul 2024 17:23:25 +0800 Subject: [PATCH 439/670] Remove unused using --- osu.Game/Overlays/Comments/CommentAuthorLine.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index 6c04f332bb..6d70de222d 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -1,18 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; 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.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; From 54e73acecea40d6d19f596aeb2d64e0f9858d7fc Mon Sep 17 00:00:00 2001 From: jkh675 Date: Wed, 31 Jul 2024 17:49:03 +0800 Subject: [PATCH 440/670] Cleanup code --- osu.Game/Overlays/Comments/CommentAuthorLine.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index 6d70de222d..fa58ce06fe 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -212,8 +212,6 @@ namespace osu.Game.Overlays.Comments MaximumSize = new Vector2(max_width, float.PositiveInfinity), } }; - - FinishTransforms(); } private LocalisableString lastPresent; From e329427d6e2c66a5c68ee9d6e9858c284abc9d5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:28:30 +0900 Subject: [PATCH 441/670] Apply nullability to `Timeline` --- .../Compose/Components/Timeline/Timeline.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 6ce5c06801..c1a16e2503 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -31,10 +29,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; [Resolved] - private EditorClock editorClock { get; set; } + private EditorClock editorClock { get; set; } = null!; [Resolved] - private EditorBeatmap editorBeatmap { get; set; } + private EditorBeatmap editorBeatmap { get; set; } = null!; /// /// The timeline's scroll position in the last frame. @@ -61,6 +59,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private float defaultTimelineZoom; + private WaveformGraph waveform = null!; + + private TimelineTickDisplay ticks = null!; + + private TimelineControlPointDisplay controlPoints = null!; + + private Container mainContent = null!; + + private Bindable waveformOpacity = null!; + private Bindable controlPointsVisible = null!; + private Bindable ticksVisible = null!; + + private double trackLengthForZoom; + + private readonly IBindable track = new Bindable(); + public Timeline(Drawable userContent) { this.userContent = userContent; @@ -73,22 +87,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline ScrollbarVisible = false; } - private WaveformGraph waveform; - - private TimelineTickDisplay ticks; - - private TimelineControlPointDisplay controlPoints; - - private Container mainContent; - - private Bindable waveformOpacity; - private Bindable controlPointsVisible; - private Bindable ticksVisible; - - private double trackLengthForZoom; - - private readonly IBindable track = new Bindable(); - [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { @@ -318,7 +316,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [Resolved] - private IBeatSnapProvider beatSnapProvider { get; set; } + private IBeatSnapProvider beatSnapProvider { get; set; } = null!; /// /// The total amount of time visible on the timeline. From 2d52bab77b7d008e296659e442a0cf2273e1b430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:43:08 +0900 Subject: [PATCH 442/670] Always show timing points in timeline when at the timing screen Supersedes https://github.com/ppy/osu/pull/29196. --- .../Compose/Components/Timeline/Timeline.cs | 17 ++++++++++++++++- .../Screens/Edit/EditorScreenWithTimeline.cs | 13 ++++++++++--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 8 ++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index c1a16e2503..7a28f7bbaa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -28,6 +28,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private bool alwaysShowControlPoints; + + public bool AlwaysShowControlPoints + { + get => alwaysShowControlPoints; + set + { + if (value == alwaysShowControlPoints) + return; + + alwaysShowControlPoints = value; + controlPointsVisible.TriggerChange(); + } + } + [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -176,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline controlPointsVisible.BindValueChanged(visible => { - if (visible.NewValue) + if (visible.NewValue || alwaysShowControlPoints) { this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint); mainContent.MoveToY(15, 200, Easing.OutQuint); diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 38d2a1e7e4..01908e45c7 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader(true)] - private void load(OverlayColourProvider colourProvider) + private void load() { // Grid with only two rows. // First is the timeline area, which should be allowed to expand as required. @@ -107,10 +106,18 @@ namespace osu.Game.Screens.Edit MainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add); + LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline => + { + ConfigureTimeline(timeline); + timelineContent.Add(timeline); + }); }); } + protected virtual void ConfigureTimeline(TimelineArea timelineArea) + { + } + protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3f911f5067..67d4429be8 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit.Compose.Components.Timeline; namespace osu.Game.Screens.Edit.Timing { @@ -53,5 +54,12 @@ namespace osu.Game.Screens.Edit.Timing SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time); } } + + protected override void ConfigureTimeline(TimelineArea timelineArea) + { + base.ConfigureTimeline(timelineArea); + + timelineArea.Timeline.AlwaysShowControlPoints = true; + } } } From 5098d637b5c86a36ce2d45be22dadeb39b6022e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jul 2024 19:55:20 +0900 Subject: [PATCH 443/670] Flash customise button on mod overlay when it becomes available --- .../Overlays/Mods/ModCustomisationHeader.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index bf10e13515..fb9e960f41 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; 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; @@ -11,14 +12,16 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osuTK; using osu.Game.Localisation; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Mods { public partial class ModCustomisationHeader : OsuHoverContainer { private Box background = null!; + private Box backgroundFlash = null!; private SpriteIcon icon = null!; [Resolved] @@ -46,6 +49,12 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, }, + backgroundFlash = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(0.4f), + Blending = BlendingParameters.Additive, + }, new OsuSpriteText { Anchor = Anchor.CentreLeft, @@ -84,6 +93,12 @@ namespace osu.Game.Overlays.Mods TooltipText = e.NewValue ? string.Empty : ModSelectOverlayStrings.CustomisationPanelDisabledReason; + + if (e.NewValue) + { + backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then() + .FadeOutFromOne(350, Easing.OutQuad); + } }, true); Expanded.BindValueChanged(v => From dab967e6be8603bfb50dc462099dd167c9cda965 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Aug 2024 18:36:33 +0900 Subject: [PATCH 444/670] Fix insane transform allocations in new leaderboard display --- .../Leaderboards/LeaderboardScoreV2.cs | 58 +++++++++++++------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs index c9584b057b..b6508e177a 100644 --- a/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs +++ b/osu.Game/Screens/SelectV2/Leaderboards/LeaderboardScoreV2.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Localisation; using osu.Game.Configuration; using osu.Game.Extensions; @@ -38,6 +37,7 @@ using osu.Game.Users.Drawables; using osu.Game.Utils; using osuTK; using osuTK.Graphics; +using CommonStrings = osu.Game.Localisation.CommonStrings; namespace osu.Game.Screens.SelectV2.Leaderboards { @@ -61,7 +61,6 @@ namespace osu.Game.Screens.SelectV2.Leaderboards private const float statistics_regular_min_width = 175; private const float statistics_compact_min_width = 100; private const float rank_label_width = 65; - private const float rank_label_visibility_width_cutoff = rank_label_width + height + username_min_width + statistics_regular_min_width + expanded_right_content_width; private readonly ScoreInfo score; private readonly bool sheared; @@ -560,33 +559,34 @@ namespace osu.Game.Screens.SelectV2.Leaderboards background.FadeColour(IsHovered ? backgroundColour.Lighten(0.2f) : backgroundColour, transition_duration, Easing.OutQuint); totalScoreBackground.FadeColour(IsHovered ? lightenedGradient : totalScoreBackgroundGradient, transition_duration, Easing.OutQuint); - if (DrawWidth < rank_label_visibility_width_cutoff && IsHovered) + if (IsHovered && currentMode != DisplayMode.Full) rankLabelOverlay.FadeIn(transition_duration, Easing.OutQuint); else rankLabelOverlay.FadeOut(transition_duration, Easing.OutQuint); } - protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) - { - Scheduler.AddOnce(() => - { - // when width decreases - // - hide rank and show rank overlay on avatar when hovered, then - // - compact statistics, then - // - hide statistics + private DisplayMode? currentMode; - if (DrawWidth >= rank_label_visibility_width_cutoff) + protected override void Update() + { + base.Update(); + + DisplayMode mode = getCurrentDisplayMode(); + + if (currentMode != mode) + { + if (mode >= DisplayMode.Full) rankLabel.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); else rankLabel.FadeOut(transition_duration, Easing.OutQuint).MoveToX(-rankLabel.DrawWidth, transition_duration, Easing.OutQuint); - if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width) + if (mode >= DisplayMode.Regular) { statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Horizontal; statisticsContainer.ScaleTo(1, transition_duration, Easing.OutQuint); } - else if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width) + else if (mode >= DisplayMode.Compact) { statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint); statisticsContainer.Direction = FillDirection.Vertical; @@ -594,13 +594,35 @@ namespace osu.Game.Screens.SelectV2.Leaderboards } else statisticsContainer.FadeOut(transition_duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, transition_duration, Easing.OutQuint); - }); - return base.OnInvalidate(invalidation, source); + currentMode = mode; + } + } + + private DisplayMode getCurrentDisplayMode() + { + if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width + rank_label_width) + return DisplayMode.Full; + + if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width) + return DisplayMode.Regular; + + if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width) + return DisplayMode.Compact; + + return DisplayMode.Minimal; } #region Subclasses + private enum DisplayMode + { + Minimal, + Compact, + Regular, + Full + } + private partial class DateLabel : DrawableDate { public DateLabel(DateTimeOffset date) @@ -749,8 +771,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards if (score.Files.Count <= 0) return items.ToArray(); - items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); - items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); + items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score))); + items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); return items.ToArray(); } From 188ddbcad68fa67a074c9a0f8860304d27d560b9 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 1 Aug 2024 18:38:01 +0800 Subject: [PATCH 445/670] pass `ModCustomisationPanel` through ctor --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 7 ++++--- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 13 +++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 306675a741..9589c7465f 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -29,10 +29,11 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Expanded = new BindableBool(); - protected new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private readonly ModCustomisationPanel panel; - public ModCustomisationHeader() + public ModCustomisationHeader(ModCustomisationPanel panel) { + this.panel = panel; Action = Expanded.Toggle; Enabled.Value = false; } @@ -113,7 +114,7 @@ namespace osu.Game.Overlays.Mods if (Enabled.Value) { if (!touchedThisFrame) - Parent?.UpdateHoverExpansion(true); + panel.UpdateHoverExpansion(true); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 9795b61762..85991c3a9d 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Mods InternalChildren = new Drawable[] { - new ModCustomisationHeader + new ModCustomisationHeader(this) { Depth = float.MinValue, RelativeSizeAxes = Axes.X, @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods Enabled = { BindTarget = Enabled }, Expanded = { BindTarget = Expanded }, }, - content = new FocusGrabbingContainer + content = new FocusGrabbingContainer(this) { RelativeSizeAxes = Axes.X, BorderColour = colourProvider.Dark3, @@ -229,12 +229,17 @@ namespace osu.Game.Overlays.Mods public override bool RequestsFocus => Expanded.Value; public override bool AcceptsFocus => Expanded.Value; - public new ModCustomisationPanel? Parent => (ModCustomisationPanel?)base.Parent; + private readonly ModCustomisationPanel panel; + + public FocusGrabbingContainer(ModCustomisationPanel panel) + { + this.panel = panel; + } protected override void OnHoverLost(HoverLostEvent e) { if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - Parent?.UpdateHoverExpansion(false); + panel.UpdateHoverExpansion(false); base.OnHoverLost(e); } From 548fd9cbf9e96b93cc69add00509e8b0491fce2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Aug 2024 19:44:42 +0900 Subject: [PATCH 446/670] Show breaks behind objects in timeline Closes https://github.com/ppy/osu/issues/29227. --- .../Screens/Edit/Compose/ComposeScreen.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index cc33840929..f7e523db25 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -69,19 +69,24 @@ namespace osu.Game.Screens.Edit.Compose if (ruleset == null || composer == null) return base.CreateTimelineContent(); + TimelineBreakDisplay breakDisplay = new TimelineBreakDisplay + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 0.75f, + }; + return wrapSkinnableContent(new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { + // We want to display this below hitobjects to better expose placement objects visually. + // It needs to be above the blueprint container to handle drags on breaks though. + breakDisplay.CreateProxy(), new TimelineBlueprintContainer(composer), - new TimelineBreakDisplay - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 0.75f, - }, + breakDisplay } }); } From 051d52c23f69db59c3a4270f29c4d7a5fb439838 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Thu, 1 Aug 2024 19:25:45 +0800 Subject: [PATCH 447/670] Update ModCustomisationPanel to use ExpandedState enum --- .../TestSceneFreeModSelectOverlay.cs | 2 +- .../TestSceneModCustomisationPanel.cs | 26 +++---- .../TestSceneModSelectOverlay.cs | 4 +- .../Overlays/Mods/ModCustomisationHeader.cs | 24 +++++-- .../Overlays/Mods/ModCustomisationPanel.cs | 68 ++++++++++--------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 14 ++-- 6 files changed, 77 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 497faa28d0..3097d24595 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); - AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded.Value); + AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 64ef7891c8..16c9c2bc14 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -51,22 +51,22 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set DT", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set DA", () => { SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set FL+WU+DA+AD", () => { SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; - panel.Enabled.Value = panel.Expanded.Value = true; + panel.Enabled.Value = panel.Expanded = true; }); AddStep("set empty", () => { SelectedMods.Value = Array.Empty(); - panel.Enabled.Value = panel.Expanded.Value = false; + panel.Enabled.Value = panel.Expanded = false; }); } @@ -77,11 +77,11 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("neither expanded", () => !panel.Expanded.Value); + AddAssert("neither expanded", () => !panel.Expanded); AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); } @@ -96,40 +96,40 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("expanded", () => panel.Expanded.Value); + AddAssert("expanded", () => panel.Expanded); AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("still expanded", () => panel.Expanded.Value); + AddAssert("still expanded", () => panel.Expanded); } // Will collapse when mouse left from content { AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); } // Will collapse when mouse left from header { AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("expanded", () => panel.Expanded.Value); + AddAssert("expanded", () => panel.Expanded); AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("not expanded", () => !panel.Expanded.Value); + AddAssert("not expanded", () => !panel.Expanded); } // Not collapse when mouse left if not expanded by hovering { - AddStep("expand not by hovering", () => panel.Expanded.Value = true); + AddStep("expand not by hovering", () => panel.Expanded = true); AddStep("hover content", () => InputManager.MoveMouseTo(content)); AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); - AddAssert("still expanded", () => panel.Expanded.Value); + AddAssert("still expanded", () => panel.Expanded); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 77909d6936..0057582755 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -999,7 +999,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded.Value); + AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded); if (textSearchStartsActive) AddAssert("search focused", () => this.ChildrenOfType().Single().HasFocus); @@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); - AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded.Value == active); + AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded == active); } private T getSelectedMod() where T : Mod => SelectedMods.Value.OfType().Single(); diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 9589c7465f..76d2a0deb1 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osu.Game.Localisation; +using static osu.Game.Overlays.Mods.ModCustomisationPanel; namespace osu.Game.Overlays.Mods { @@ -27,14 +28,13 @@ namespace osu.Game.Overlays.Mods protected override IEnumerable EffectTargets => new[] { background }; - public readonly BindableBool Expanded = new BindableBool(); + public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); private readonly ModCustomisationPanel panel; public ModCustomisationHeader(ModCustomisationPanel panel) { this.panel = panel; - Action = Expanded.Toggle; Enabled.Value = false; } @@ -90,12 +90,26 @@ namespace osu.Game.Overlays.Mods : ModSelectOverlayStrings.CustomisationPanelDisabledReason; }, true); - Expanded.BindValueChanged(v => + ExpandedState.BindValueChanged(v => { - icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); + icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint); }, true); } + protected override bool OnClick(ClickEvent e) + { + if (Enabled.Value) + { + ExpandedState.Value = ExpandedState.Value switch + { + ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded, + _ => ModCustomisationPanelState.Collapsed + }; + } + + return base.OnClick(e); + } + private bool touchedThisFrame; protected override bool OnTouchDown(TouchDownEvent e) @@ -114,7 +128,7 @@ namespace osu.Game.Overlays.Mods if (Enabled.Value) { if (!touchedThisFrame) - panel.UpdateHoverExpansion(true); + panel.UpdateHoverExpansion(ModCustomisationPanelState.ExpandedByHover); } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index 85991c3a9d..a551081a7b 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -39,7 +38,13 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Enabled = new BindableBool(); - public readonly BindableBool Expanded = new BindableBool(); + public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); + + public bool Expanded + { + get => ExpandedState.Value > ModCustomisationPanelState.Collapsed; + set => ExpandedState.Value = value ? ModCustomisationPanelState.Expanded : ModCustomisationPanelState.Collapsed; + } public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); @@ -48,8 +53,8 @@ namespace osu.Game.Overlays.Mods // Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded. // These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside // (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work). - public override bool HandlePositionalInput => Expanded.Value; - public override bool HandleNonPositionalInput => Expanded.Value; + public override bool HandlePositionalInput => Expanded; + public override bool HandleNonPositionalInput => Expanded; [BackgroundDependencyLoader] private void load() @@ -64,7 +69,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Height = header_height, Enabled = { BindTarget = Enabled }, - Expanded = { BindTarget = Expanded }, + ExpandedState = { BindTarget = ExpandedState }, }, content = new FocusGrabbingContainer(this) { @@ -81,8 +86,7 @@ namespace osu.Game.Overlays.Mods Roundness = 5f, Colour = Color4.Black.Opacity(0.25f), }, - Expanded = { BindTarget = Expanded }, - ExpandedByHovering = { BindTarget = ExpandedByHovering }, + ExpandedState = { BindTarget = ExpandedState }, Children = new Drawable[] { new Box @@ -124,7 +128,7 @@ namespace osu.Game.Overlays.Mods this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint); }, true); - Expanded.BindValueChanged(_ => updateDisplay(), true); + ExpandedState.BindValueChanged(_ => updateDisplay(), true); SelectedMods.BindValueChanged(_ => updateMods(), true); FinishTransforms(true); @@ -136,7 +140,7 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { - Expanded.Value = false; + Expanded = false; return base.OnClick(e); } @@ -149,7 +153,7 @@ namespace osu.Game.Overlays.Mods switch (e.Action) { case GlobalAction.Back: - Expanded.Value = false; + Expanded = false; return true; } @@ -164,7 +168,7 @@ namespace osu.Game.Overlays.Mods { content.ClearTransforms(); - if (Expanded.Value) + if (Expanded) { content.AutoSizeDuration = 400; content.AutoSizeEasing = Easing.OutQuint; @@ -177,30 +181,19 @@ namespace osu.Game.Overlays.Mods content.ResizeHeightTo(header_height, 400, Easing.OutQuint); content.FadeOut(400, Easing.OutSine); } - - ExpandedByHovering.Value = false; } - public readonly BindableBool ExpandedByHovering = new BindableBool(); - - public void UpdateHoverExpansion(bool hovered) + public void UpdateHoverExpansion(ModCustomisationPanelState state) { - if (hovered && !Expanded.Value) - { - Expanded.Value = true; - ExpandedByHovering.Value = true; - } - else if (!hovered && ExpandedByHovering.Value) - { - Debug.Assert(Expanded.Value); + if (state > ModCustomisationPanelState.Collapsed && state <= ExpandedState.Value) + return; - Expanded.Value = false; - } + ExpandedState.Value = state; } private void updateMods() { - Expanded.Value = false; + Expanded = false; sectionsFlow.Clear(); // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). @@ -223,11 +216,10 @@ namespace osu.Game.Overlays.Mods private partial class FocusGrabbingContainer : InputBlockingContainer { - public IBindable Expanded { get; } = new BindableBool(); - public IBindable ExpandedByHovering { get; } = new BindableBool(); + public readonly IBindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); - public override bool RequestsFocus => Expanded.Value; - public override bool AcceptsFocus => Expanded.Value; + public override bool RequestsFocus => panel.Expanded; + public override bool AcceptsFocus => panel.Expanded; private readonly ModCustomisationPanel panel; @@ -238,11 +230,21 @@ namespace osu.Game.Overlays.Mods protected override void OnHoverLost(HoverLostEvent e) { - if (ExpandedByHovering.Value && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - panel.UpdateHoverExpansion(false); + if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover + && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + { + panel.UpdateHoverExpansion(ModCustomisationPanelState.Collapsed); + } base.OnHoverLost(e); } } + + public enum ModCustomisationPanelState + { + Collapsed = 0, + ExpandedByHover = 1, + Expanded = 2, + } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7469590895..109d81f779 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Mods ActiveMods.Value = ComputeActiveMods(); }, true); - customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true); + customisationPanel.ExpandedState.BindValueChanged(_ => updateCustomisationVisualState(), true); SearchTextBox.Current.BindValueChanged(query => { @@ -368,18 +368,18 @@ namespace osu.Game.Overlays.Mods customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) - customisationPanel.Expanded.Value = true; + customisationPanel.Expanded = true; } else { - customisationPanel.Expanded.Value = false; + customisationPanel.Expanded = false; customisationPanel.Enabled.Value = false; } } private void updateCustomisationVisualState() { - if (customisationPanel.Expanded.Value) + if (customisationPanel.Expanded) { columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); @@ -544,7 +544,7 @@ namespace osu.Game.Overlays.Mods nonFilteredColumnCount += 1; } - customisationPanel.Expanded.Value = false; + customisationPanel.Expanded = false; } #endregion @@ -571,7 +571,7 @@ namespace osu.Game.Overlays.Mods // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { - if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value) + if (!SearchTextBox.HasFocus && !customisationPanel.Expanded) { DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; @@ -637,7 +637,7 @@ namespace osu.Game.Overlays.Mods if (e.Repeat || e.Key != Key.Tab) return false; - if (customisationPanel.Expanded.Value) + if (customisationPanel.Expanded) return true; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) From b883ff6c7be16f2ef6d6bd8456fda38da45d5ba7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:18:00 -0700 Subject: [PATCH 448/670] Fix click sounds playing twice on `OsuRearrangeableListItem` --- osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs index 39a3edb82c..445588d525 100644 --- a/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs +++ b/osu.Game/Graphics/Containers/OsuRearrangeableListItem.cs @@ -64,6 +64,7 @@ namespace osu.Game.Graphics.Containers { InternalChildren = new Drawable[] { + new HoverClickSounds(), new GridContainer { RelativeSizeAxes = Axes.X, @@ -92,7 +93,6 @@ namespace osu.Game.Graphics.Containers ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }, - new HoverClickSounds() }; } From 0fac8148ed987bcc9f4b3340719bdd77fe57b110 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:30:52 -0700 Subject: [PATCH 449/670] Fix collection delete button not having hover click sounds --- .../Collections/DrawableCollectionListItem.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 596bb5d673..3b7649a30c 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -132,7 +132,7 @@ namespace osu.Game.Collections } } - public partial class DeleteButton : CompositeDrawable + public partial class DeleteButton : OsuClickableContainer { public Func IsTextBoxHovered = null!; @@ -155,7 +155,7 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChild = fadeContainer = new Container + Child = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, Alpha = 0.1f, @@ -176,6 +176,14 @@ namespace osu.Game.Collections } } }; + + Action = () => + { + if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) + deleteCollection(); + else + dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); + }; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); @@ -195,12 +203,7 @@ namespace osu.Game.Collections { background.FlashColour(Color4.White, 150); - if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) - deleteCollection(); - else - dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); - - return true; + return base.OnClick(e); } private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c)); From 1e38d1fa57c1fc37791ce7a58f9f1c1de05e6162 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 1 Aug 2024 18:45:47 -0700 Subject: [PATCH 450/670] Apply corner radius at a higher level so hover click sounds account for it --- osu.Game/Collections/DrawableCollectionListItem.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 3b7649a30c..e71368c079 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -43,6 +43,9 @@ namespace osu.Game.Collections // // if we want to support user sorting (but changes will need to be made to realm to persist). ShowDragHandle.Value = false; + + Masking = true; + CornerRadius = item_height / 2; } protected override Drawable CreateContent() => new ItemContent(Model); @@ -50,7 +53,7 @@ namespace osu.Game.Collections /// /// The main content of the . /// - private partial class ItemContent : CircularContainer + private partial class ItemContent : CompositeDrawable { private readonly Live collection; @@ -65,13 +68,12 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.X; Height = item_height; - Masking = true; } [BackgroundDependencyLoader] private void load() { - Children = new[] + InternalChildren = new[] { collection.IsManaged ? new DeleteButton(collection) From 894c6150c8b180e380a5e7d541ef0c306bf05dc3 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Fri, 2 Aug 2024 11:59:28 +0800 Subject: [PATCH 451/670] Revert "Update resources" This reverts commit cbfb569ad47d17a6de63dd6484d3c9a15cd2d452. --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d8b643279..e7d9d4c022 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 5de37f9cd5fe4ba2784dd233af85044804b3f5b9 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Fri, 2 Aug 2024 12:02:28 +0800 Subject: [PATCH 452/670] Revert changes --- .../Overlays/Comments/CommentAuthorLine.cs | 61 +------------------ osu.Game/osu.Game.csproj | 2 +- 2 files changed, 4 insertions(+), 59 deletions(-) diff --git a/osu.Game/Overlays/Comments/CommentAuthorLine.cs b/osu.Game/Overlays/Comments/CommentAuthorLine.cs index fa58ce06fe..1f6fef4df3 100644 --- a/osu.Game/Overlays/Comments/CommentAuthorLine.cs +++ b/osu.Game/Overlays/Comments/CommentAuthorLine.cs @@ -139,13 +139,11 @@ namespace osu.Game.Overlays.Comments } } - private partial class ParentUsername : FillFlowContainer, IHasCustomTooltip + private partial class ParentUsername : FillFlowContainer, IHasTooltip { - public ITooltip GetCustomTooltip() => new CommentTooltip(); + public LocalisableString TooltipText => getParentMessage(); - LocalisableString IHasCustomTooltip.TooltipContent => getParentMessage(); - - private Comment? parentComment { get; } + private readonly Comment? parentComment; public ParentUsername(Comment comment) { @@ -178,58 +176,5 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? CommentsStrings.Deleted : string.Empty; } } - - private partial class CommentTooltip : VisibilityContainer, ITooltip - { - private const int max_width = 500; - - private TextFlowContainer content { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.Both; - - Masking = true; - CornerRadius = 7; - - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both - }, - content = new TextFlowContainer(f => - { - f.Font = OsuFont.Default; - f.Truncate = true; - f.MaxWidth = max_width; - }) - { - Margin = new MarginPadding(3), - AutoSizeAxes = Axes.Both, - MaximumSize = new Vector2(max_width, float.PositiveInfinity), - } - }; - } - - private LocalisableString lastPresent; - - public void SetContent(LocalisableString content) - { - if (lastPresent.Equals(content)) - return; - - this.content.Text = content; - lastPresent = content; - } - - public void Move(Vector2 pos) => Position = pos; - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7d9d4c022..3d8b643279 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From f6ca4b233913916d4ca19a7efee3b61345b90172 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Fri, 2 Aug 2024 12:16:50 +0800 Subject: [PATCH 453/670] Replace the `OsuSpriteText` with `TextFlowContainer` in `OsuTooltip` and limit the max width --- .../Graphics/Cursor/OsuTooltipContainer.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index aab5b3ee36..cc95a5bd2b 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Containers; namespace osu.Game.Graphics.Cursor { @@ -27,13 +27,18 @@ namespace osu.Game.Graphics.Cursor public partial class OsuTooltip : Tooltip { + private const float max_width = 1024; + private readonly Box background; - private readonly OsuSpriteText text; + private readonly TextFlowContainer text; private bool instantMovement = true; + private LocalisableString lastPresent; + public override void SetContent(LocalisableString contentString) { - if (contentString == text.Text) return; + if (contentString.Equals(lastPresent)) + return; text.Text = contentString; @@ -44,6 +49,8 @@ namespace osu.Game.Graphics.Cursor } else AutoSizeDuration = 0; + + lastPresent = contentString; } public OsuTooltip() @@ -65,10 +72,16 @@ namespace osu.Game.Graphics.Cursor RelativeSizeAxes = Axes.Both, Alpha = 0.9f, }, - text = new OsuSpriteText + text = new TextFlowContainer(f => { - Padding = new MarginPadding(5), - Font = OsuFont.GetFont(weight: FontWeight.Regular) + f.Font = OsuFont.GetFont(weight: FontWeight.Regular); + f.Truncate = true; + f.MaxWidth = max_width; + }) + { + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + MaximumSize = new Vector2(max_width, float.PositiveInfinity), } }; } From 3c1907ced3b084593fb3bc6e3796b811aa717dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 14:48:49 +0900 Subject: [PATCH 454/670] Update `LocalisationAnalyser` to latest version --- .config/dotnet-tools.json | 2 +- osu.Game/osu.Game.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index ace7db82f8..c4ba6e5143 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -21,7 +21,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2024.517.0", + "version": "2024.802.0", "commands": [ "localisation" ] diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d8b643279..c25f16f1b0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From d9c965c47b4cc2291403ca5fbe7b4619c3541eff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:27:21 +0900 Subject: [PATCH 455/670] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e7d9d4c022..2ac0864266 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From d76fc34cf8e9f96890ebc3c2bc3e0ea2c224702e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:28:54 +0900 Subject: [PATCH 456/670] Update to use localiastions --- .../Components/DailyChallengeStreakDisplay.cs | 10 +++--- .../Components/DailyChallengeStreakTooltip.cs | 31 +++++++------------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs index da0e334a4e..289e820e4a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components { @@ -50,8 +52,7 @@ namespace osu.Game.Overlays.Profile.Header.Components new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.Both, - // Text = UsersStrings.ShowDailyChallengeTitle - Text = "Daily\nChallenge", + Text = UsersStrings.ShowDailyChallengeTitle, Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, }, new Container @@ -97,9 +98,8 @@ namespace osu.Game.Overlays.Profile.Header.Components } var statistics = User.Value.User.DailyChallengeStatistics; - // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount); - dailyStreak.Text = $"{statistics.PlayCount}d"; - TooltipContent = new DailyChallengeStreakTooltipData(colourProvider, statistics); + dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount.ToLocalisableString("N0")); + TooltipContent = new DailyChallengeTooltipData(colourProvider, statistics); Show(); } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs index a105659ac7..d33c7d9504 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -13,6 +13,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osuTK; using Box = osu.Framework.Graphics.Shapes.Box; @@ -78,10 +79,8 @@ namespace osu.Game.Overlays.Profile.Header.Components Spacing = new Vector2(30f), Children = new[] { - // currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), - // currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), - currentDaily = new StreakPiece("Current Daily Streak"), - currentWeekly = new StreakPiece("Current Weekly Streak"), + currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent), + currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent), } }, } @@ -94,14 +93,10 @@ namespace osu.Game.Overlays.Profile.Header.Components Spacing = new Vector2(10f), Children = new[] { - // bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), - // bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), - // topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), - // topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), - bestDaily = new StatisticsPiece("Best Daily Streak"), - bestWeekly = new StatisticsPiece("Best Weekly Streak"), - topTen = new StatisticsPiece("Top 10% Placements"), - topFifty = new StatisticsPiece("Top 50% Placements"), + bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest), + bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest), + topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements), + topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements), } }, } @@ -117,20 +112,16 @@ namespace osu.Game.Overlays.Profile.Header.Components background.Colour = colourProvider.Background4; topBackground.Colour = colourProvider.Background5; - // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); - currentDaily.Value = $"{statistics.DailyStreakCurrent:N0}d"; + currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); - // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); - currentWeekly.Value = $"{statistics.WeeklyStreakCurrent:N0}w"; + currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); - bestDaily.Value = $"{statistics.DailyStreakBest:N0}d"; + bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); - bestWeekly.Value = $"{statistics.WeeklyStreakBest:N0}w"; + bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); From 816dee181ab34412940259e89d42eb9cc77230a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:31:22 +0900 Subject: [PATCH 457/670] Rename classes to remove "streak" terminology Since the primary display isn't showing a streak. --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 4 ++-- ...llengeStreakDisplay.cs => DailyChallengeStatsDisplay.cs} | 6 +++--- ...llengeStreakTooltip.cs => DailyChallengeStatsTooltip.cs} | 6 +++--- osu.Game/Overlays/Profile/Header/Components/MainDetails.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game/Overlays/Profile/Header/Components/{DailyChallengeStreakDisplay.cs => DailyChallengeStatsDisplay.cs} (92%) rename osu.Game/Overlays/Profile/Header/Components/{DailyChallengeStreakTooltip.cs => DailyChallengeStatsTooltip.cs} (96%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index c0fb7b49f0..f2135ec992 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online { base.LoadComplete(); - DailyChallengeStreakDisplay display = null!; + DailyChallengeStatsDisplay display = null!; AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v)); AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v)); @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background2, }); - Add(display = new DailyChallengeStreakDisplay + Add(display = new DailyChallengeStatsDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs similarity index 92% rename from osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs rename to osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 289e820e4a..e154909139 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -15,11 +15,11 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip { public readonly Bindable User = new Bindable(); - public DailyChallengeStreakTooltipData? TooltipContent { get; private set; } + public DailyChallengeTooltipData? TooltipContent { get; private set; } private OsuSpriteText dailyStreak = null!; @@ -103,6 +103,6 @@ namespace osu.Game.Overlays.Profile.Header.Components Show(); } - public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(); + public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs similarity index 96% rename from osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs rename to osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index d33c7d9504..1b54633b8a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -21,7 +21,7 @@ using Color4 = osuTK.Graphics.Color4; namespace osu.Game.Overlays.Profile.Header.Components { - public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip { private StreakPiece currentDaily = null!; private StreakPiece currentWeekly = null!; @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Profile.Header.Components }; } - public void SetContent(DailyChallengeStreakTooltipData content) + public void SetContent(DailyChallengeTooltipData content) { var statistics = content.Statistics; var colourProvider = content.ColourProvider; @@ -237,5 +237,5 @@ namespace osu.Game.Overlays.Profile.Header.Components } } - public record DailyChallengeStreakTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); + public record DailyChallengeTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics); } diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index f9a4267ed9..3d97082230 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { Title = UsersStrings.ShowRankCountrySimple, }, - new DailyChallengeStreakDisplay + new DailyChallengeStatsDisplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From 729039406bbd32102a2706f207a0c1fcbeba0f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 15:37:22 +0900 Subject: [PATCH 458/670] Add colouring for play count Matches https://github.com/ppy/osu-web/pull/11381. --- .../Components/DailyChallengeStatsDisplay.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index e154909139..50c9b6e1f9 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -11,7 +11,9 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Header.Components { @@ -21,7 +23,10 @@ namespace osu.Game.Overlays.Profile.Header.Components public DailyChallengeTooltipData? TooltipContent { get; private set; } - private OsuSpriteText dailyStreak = null!; + private OsuSpriteText dailyPlayCount = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -68,7 +73,7 @@ namespace osu.Game.Overlays.Profile.Header.Components RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background6, }, - dailyStreak = new OsuSpriteText + dailyPlayCount = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -97,10 +102,16 @@ namespace osu.Game.Overlays.Profile.Header.Components return; } - var statistics = User.Value.User.DailyChallengeStatistics; - dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.PlayCount.ToLocalisableString("N0")); - TooltipContent = new DailyChallengeTooltipData(colourProvider, statistics); + APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; + + dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0")); + dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount)); + + TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); + Show(); + + static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3); } public ITooltip GetCustomTooltip() => new DailyChallengeStatsTooltip(); From c3b2d81066d9885e263dc150158aed78cfcefb46 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 09:23:25 +0300 Subject: [PATCH 459/670] Add failing test case --- .../Gameplay/TestScenePauseInputHandling.cs | 107 +++++++++++++----- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index d778f2e991..a6e062242c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -13,10 +14,12 @@ using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; + using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -32,6 +35,23 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private AudioManager audioManager { get; set; } = null!; + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + HitObjects = + { + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 0, + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE / 2, + StartTime = 5000, + } + } + }; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager); @@ -70,18 +90,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - - // Z key was released before pause, resuming should not trigger it - checkKey(() => counter, 1, false); - - AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); - checkKey(() => counter, 1, false); - - AddStep("press Z", () => InputManager.PressKey(Key.Z)); checkKey(() => counter, 2, true); AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); checkKey(() => counter, 2, false); + + AddStep("press Z", () => InputManager.PressKey(Key.Z)); + checkKey(() => counter, 3, true); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counter, 3, false); } [Test] @@ -90,30 +108,29 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); checkKey(() => counter, 0, false); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, false); AddStep("pause", () => Player.Pause()); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, false); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, false); AddStep("resume", () => Player.Resume()); AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); - checkKey(() => counter, 1, false); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 2, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 2, false); } @@ -145,8 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - checkKey(() => counterZ, 1, false); + checkKey(() => counterZ, 2, true); checkKey(() => counterX, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 2, false); } [Test] @@ -155,12 +175,12 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("pause", () => Player.Pause()); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, true); AddStep("resume", () => Player.Resume()); @@ -202,12 +222,14 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("resume", () => Player.Resume()); AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); - checkKey(() => counterZ, 1, false); + checkKey(() => counterZ, 2, true); checkKey(() => counterX, 1, true); AddStep("release X", () => InputManager.ReleaseKey(Key.X)); - checkKey(() => counterZ, 1, false); checkKey(() => counterX, 1, false); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + checkKey(() => counterZ, 2, false); } [Test] @@ -216,24 +238,50 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); AddStep("pause", () => Player.Pause()); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); - AddStep("press D", () => InputManager.PressKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); + AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("resume", () => Player.Resume()); AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning); checkKey(() => counter, 1, true); - AddStep("release D", () => InputManager.ReleaseKey(Key.D)); + AddStep("release space", () => InputManager.ReleaseKey(Key.Space)); checkKey(() => counter, 1, false); } + [Test] + public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked() + { + KeyCounter counter = null!; + + loadPlayer(() => new OsuRuleset()); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == OsuAction.LeftButton)); + + AddStep("pause", () => Player.Pause()); + AddStep("resume", () => Player.Resume()); + AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("press Z to resume", () => InputManager.PressKey(Key.Z)); + + // ensure the input manager receives the Z button press... + checkKey(() => counter, 1, true); + AddAssert("button is pressed in kbc", () => Player.DrawableRuleset.Playfield.FindClosestParent()!.PressedActions.Single() == OsuAction.LeftButton); + + // ...but also ensure the hit circle in front of the cursor isn't hit by checking max combo. + AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(0)); + + AddStep("release Z", () => InputManager.ReleaseKey(Key.Z)); + + checkKey(() => counter, 1, false); + AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent()!.PressedActions.Any()); + } + private void loadPlayer(Func createRuleset) { AddStep("set ruleset", () => currentRuleset = createRuleset()); @@ -241,9 +289,10 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType().All(s => s.ComponentsLoaded)); - AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000)); - AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500)); + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); + AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500)); AddAssert("not in break", () => !Player.IsBreakTime.Value); + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield)); } private void checkKey(Func counter, int count, bool active) From eafc0f79afcbc64fd1289ebbc8772d1192e5a6b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 10:21:44 +0300 Subject: [PATCH 460/670] Fix clicking resume cursor not triggering a gameplay press in osu! --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index d809f2b318..6970e7db1e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -115,10 +115,7 @@ namespace osu.Game.Rulesets.Osu.UI return false; scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint); - - // When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score. - // To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events. - Schedule(() => ResumeRequested?.Invoke()); + ResumeRequested?.Invoke(); return true; } From 5368a43633009f2a158a621d34c2a7fce84bd3e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 10:22:01 +0300 Subject: [PATCH 461/670] Fix clicking resume overlay hitting underlying hit circle --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++++ osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 46 +++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index df7f279656..dbb63a98c2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -206,6 +206,15 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); + private OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker; + + public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker) + { + Debug.Assert(this.resumeInputBlocker == null); + this.resumeInputBlocker = resumeInputBlocker; + AddInternal(resumeInputBlocker); + } + private partial class ProxyContainer : LifetimeManagementContainer { public void Add(Drawable proxy) => AddInternal(proxy); diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 6970e7db1e..39a77d0b42 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -33,9 +33,26 @@ namespace osu.Game.Rulesets.Osu.UI [BackgroundDependencyLoader] private void load() { + OsuResumeOverlayInputBlocker? inputBlocker = null; + + if (drawableRuleset != null) + { + var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield; + osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker()); + } + Add(cursorScaleContainer = new Container { - Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume } + Child = clickToResumeCursor = new OsuClickToResumeCursor + { + ResumeRequested = () => + { + if (inputBlocker != null) + inputBlocker.BlockNextPress = true; + + Resume(); + } + } }); } @@ -140,5 +157,32 @@ namespace osu.Game.Rulesets.Osu.UI this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); } } + + public partial class OsuResumeOverlayInputBlocker : Drawable, IKeyBindingHandler + { + public bool BlockNextPress; + + public OsuResumeOverlayInputBlocker() + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + try + { + return BlockNextPress; + } + finally + { + BlockNextPress = false; + } + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } } } From 76904272e6e153d2f78514f76de76afe08cceabc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 16:56:34 +0900 Subject: [PATCH 462/670] Allow horizontal scrolling on mod select overlay anywhere on the screen Closes https://github.com/ppy/osu/issues/29248. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7469590895..858992b8ba 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -668,6 +668,8 @@ namespace osu.Game.Overlays.Mods [Cached] internal partial class ColumnScrollContainer : OsuScrollContainer { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public ColumnScrollContainer() : base(Direction.Horizontal) { From f5a3eb56120503d3fc4ae23fb4efcc2c6a711b0e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 11:01:40 +0300 Subject: [PATCH 463/670] Add comment --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 39a77d0b42..44a2be0024 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -47,6 +47,10 @@ namespace osu.Game.Rulesets.Osu.UI { ResumeRequested = () => { + // since the user had to press a button to tap the resume cursor, + // block that press event from potentially reaching a hit circle that's behind the cursor. + // we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one, + // so we rely on a dedicated input blocking component that's implanted in there to do that for us. if (inputBlocker != null) inputBlocker.BlockNextPress = true; From dc9f6a07cb751845bb871f88d21ccb614256d0b2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 2 Aug 2024 11:16:32 +0300 Subject: [PATCH 464/670] Fix inspections --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index dbb63a98c2..7d9f5eb1a8 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); - private OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker; + private OsuResumeOverlay.OsuResumeOverlayInputBlocker? resumeInputBlocker; public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index a6e062242c..2d03d0cb7c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -19,7 +19,6 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; - using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay From 06af8cb9522fae15daead3c3892301fc5fe1cc46 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 2 Aug 2024 16:23:37 +0800 Subject: [PATCH 465/670] interpolate parts in local space to avoid broken trails --- .../UI/Cursor/CursorTrail.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 5e8061bb6a..f684bcb58f 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } AddLayout(partSizeCache); + AddLayout(scaleRatioCache); } [BackgroundDependencyLoader] @@ -154,8 +155,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return base.OnMouseMove(e); } + private readonly LayoutValue scaleRatioCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); + + private Vector2 scaleRatio => scaleRatioCache.IsValid + ? scaleRatioCache.Value + : (scaleRatioCache.Value = DrawInfo.MatrixInverse.ExtractScale().Xy); + protected void AddTrail(Vector2 position) { + position = ToLocalSpace(position); + if (InterpolateMovements) { if (!lastPosition.HasValue) @@ -174,10 +183,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f * IntervalMultiplier; - float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0); + Vector2 interval = partSize.X / 2.5f * IntervalMultiplier * scaleRatio; + float stopAt = distance - (AvoidDrawingNearCursor ? interval.Length : 0); - for (float d = interval; d < stopAt; d += interval) + for (Vector2 d = interval; d.Length < stopAt; d += interval) { lastPosition = pos1 + direction * d; addPart(lastPosition.Value); @@ -191,9 +200,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - private void addPart(Vector2 screenSpacePosition) + private void addPart(Vector2 localSpacePosition) { - parts[currentIndex].Position = ToLocalSpace(screenSpacePosition); + parts[currentIndex].Position = localSpacePosition; parts[currentIndex].Time = time + 1; ++parts[currentIndex].InvalidationID; From 4b5c163d93d912cd730d07ae9e76eeb7554a04e7 Mon Sep 17 00:00:00 2001 From: Caiyi Shyu Date: Fri, 2 Aug 2024 17:45:05 +0800 Subject: [PATCH 466/670] remove unnecessary LayoutValue --- .../UI/Cursor/CursorTrail.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index f684bcb58f..6452444fed 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shaders.Types; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.Layout; using osu.Framework.Timing; using osuTK; using osuTK.Graphics; @@ -63,9 +62,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor // -1 signals that the part is unusable, and should not be drawn parts[i].InvalidationID = -1; } - - AddLayout(partSizeCache); - AddLayout(scaleRatioCache); } [BackgroundDependencyLoader] @@ -96,12 +92,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } } - private readonly LayoutValue partSizeCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); - - private Vector2 partSize => partSizeCache.IsValid - ? partSizeCache.Value - : (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy); - /// /// The amount of time to fade the cursor trail pieces. /// @@ -155,12 +145,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor return base.OnMouseMove(e); } - private readonly LayoutValue scaleRatioCache = new LayoutValue(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence); - - private Vector2 scaleRatio => scaleRatioCache.IsValid - ? scaleRatioCache.Value - : (scaleRatioCache.Value = DrawInfo.MatrixInverse.ExtractScale().Xy); - protected void AddTrail(Vector2 position) { position = ToLocalSpace(position); @@ -183,10 +167,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - Vector2 interval = partSize.X / 2.5f * IntervalMultiplier * scaleRatio; - float stopAt = distance - (AvoidDrawingNearCursor ? interval.Length : 0); + float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier; + float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0); - for (Vector2 d = interval; d.Length < stopAt; d += interval) + for (float d = interval; d < stopAt; d += interval) { lastPosition = pos1 + direction * d; addPart(lastPosition.Value); From 64b7bab4fbd6f08d3848d18673c0b78d8b33ab91 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Aug 2024 18:59:21 +0900 Subject: [PATCH 467/670] Fix mod panels overflowing into the column borders --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 5ffed24e7a..8a499a391c 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -138,6 +138,7 @@ namespace osu.Game.Overlays.Mods }, new GridContainer { + Padding = new MarginPadding { Top = 1, Bottom = 3 }, RelativeSizeAxes = Axes.Both, RowDimensions = new[] { From 8265e7ce31a6025b12341ef2f9b0df70a103d283 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Fri, 2 Aug 2024 19:44:55 +0800 Subject: [PATCH 468/670] Reduce the tooltip max width --- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index cc95a5bd2b..5c84f5263f 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Cursor public partial class OsuTooltip : Tooltip { - private const float max_width = 1024; + private const float max_width = 500; private readonly Box background; private readonly TextFlowContainer text; From 531cf64ddbbf2e1673f63e38dfe54423a946779f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 13:15:05 +0900 Subject: [PATCH 469/670] Add failing test showing date added changing when importing as update with no change --- .../Database/BeatmapImporterUpdateTests.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index a47da4d505..3f1bc58147 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -259,6 +259,44 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestNoChangesAfterDelete() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchive(out string pathOriginalSecond); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + importBeforeUpdate!.PerformWrite(s => s.DeletePending = true); + + var dateBefore = importBeforeUpdate.Value.DateAdded; + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value); + + realm.Run(r => r.Refresh()); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, 1); + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + + Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID)); + }); + } + [Test] public void TestNoChanges() { @@ -272,21 +310,25 @@ namespace osu.Game.Tests.Database var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + var dateBefore = importBeforeUpdate!.Value.DateAdded; + Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value); + realm.Run(r => r.Refresh()); + Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); - realm.Run(r => r.Refresh()); - checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); + Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore)); Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID)); }); } From dc73856f76fe71404aeb512ae4233be00309185b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 20:45:57 +0900 Subject: [PATCH 470/670] Fix original date not being restored when no changes are made on an import-as-update operation --- osu.Game/Beatmaps/BeatmapImporter.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 71aa5b0333..8acaebd1a8 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -43,6 +43,8 @@ namespace osu.Game.Beatmaps public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { + var originalDateAdded = original.DateAdded; + Guid originalId = original.ID; var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false); @@ -57,8 +59,11 @@ namespace osu.Game.Beatmaps // If there were no changes, ensure we don't accidentally nuke ourselves. if (first.ID == originalId) { - first.PerformRead(s => + first.PerformWrite(s => { + // Transfer local values which should be persisted across a beatmap update. + s.DateAdded = originalDateAdded; + // Re-run processing even in this case. We might have outdated metadata. ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst); }); @@ -79,7 +84,7 @@ namespace osu.Game.Beatmaps original.DeletePending = true; // Transfer local values which should be persisted across a beatmap update. - updated.DateAdded = original.DateAdded; + updated.DateAdded = originalDateAdded; transferCollectionReferences(realm, original, updated); @@ -278,6 +283,9 @@ namespace osu.Game.Beatmaps protected override void UndeleteForReuse(BeatmapSetInfo existing) { + if (!existing.DeletePending) + return; + base.UndeleteForReuse(existing); existing.DateAdded = DateTimeOffset.UtcNow; } From c27b35ad14fa50e6f35ac6b8188902e3f189a79d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Aug 2024 20:58:52 +0900 Subject: [PATCH 471/670] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7785cb3c94..3b3385ecfe 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index dceb88c6f7..196d5594ad 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From a8141bf15fc1801616828c213858f68926dc8868 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Fri, 2 Aug 2024 21:50:24 +0800 Subject: [PATCH 472/670] Only wrap by per word --- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 5c84f5263f..fb5122bb93 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -75,8 +75,6 @@ namespace osu.Game.Graphics.Cursor text = new TextFlowContainer(f => { f.Font = OsuFont.GetFont(weight: FontWeight.Regular); - f.Truncate = true; - f.MaxWidth = max_width; }) { Margin = new MarginPadding(5), From 4ef9f335eef73f607ccd6d2e7ab8bb6d3003775e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 2 Aug 2024 10:19:59 -0700 Subject: [PATCH 473/670] Fix customise button on mod overlay initially showing flash layer indefinitely --- osu.Game/Overlays/Mods/ModCustomisationHeader.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index fb9e960f41..540ed8ee94 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -54,6 +54,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Both, Colour = Color4.White.Opacity(0.4f), Blending = BlendingParameters.Additive, + Alpha = 0, }, new OsuSpriteText { From 040f65432ebcd54bc639d505b94990a986564789 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 3 Aug 2024 19:39:49 +0900 Subject: [PATCH 474/670] Rename variables a bit --- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index fb5122bb93..0d36cc1d08 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -33,14 +33,14 @@ namespace osu.Game.Graphics.Cursor private readonly TextFlowContainer text; private bool instantMovement = true; - private LocalisableString lastPresent; + private LocalisableString lastContent; - public override void SetContent(LocalisableString contentString) + public override void SetContent(LocalisableString content) { - if (contentString.Equals(lastPresent)) + if (content.Equals(lastContent)) return; - text.Text = contentString; + text.Text = content; if (IsPresent) { @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Cursor else AutoSizeDuration = 0; - lastPresent = contentString; + lastContent = content; } public OsuTooltip() From d95d63d7ee4a9c2a925fdbfb3cea6a8f642ec7c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Aug 2024 22:44:51 +0900 Subject: [PATCH 475/670] Undo localisation of Daily Challenge string for now --- .../Profile/Header/Components/DailyChallengeStatsDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index 50c9b6e1f9..f55eb595d7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -57,7 +57,9 @@ namespace osu.Game.Overlays.Profile.Header.Components new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.Both, - Text = UsersStrings.ShowDailyChallengeTitle, + // can't use this because osu-web does weird stuff with \\n. + // Text = UsersStrings.ShowDailyChallengeTitle., + Text = "Daily\nChallenge", Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f }, }, new Container From 2daf1b58f2dcdbd0fbf308547d99c55d6f667f2e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 3 Aug 2024 14:48:08 -0700 Subject: [PATCH 476/670] Allow searching enum descriptions from `SettingsEnumDropdown`s --- osu.Game/Overlays/Settings/SettingsEnumDropdown.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index cf6bc30f85..2b74557c1a 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -2,7 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -10,6 +14,8 @@ namespace osu.Game.Overlays.Settings public partial class SettingsEnumDropdown : SettingsDropdown where T : struct, Enum { + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.GetLocalisableDescription())); + protected override OsuDropdown CreateDropdown() => new DropdownControl(); protected new partial class DropdownControl : OsuEnumDropdown From 6f9866d542d14f365294579c44df7564ada34337 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 18:56:19 +0800 Subject: [PATCH 477/670] Add unit test for `OsuTooltip` --- .../UserInterface/TestSceneOsuTooltip.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs new file mode 100644 index 0000000000..1a1db8eaf7 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.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.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public partial class TestSceneOsuTooltip : OsuManualInputManagerTestScene + { + private TestTooltipContainer container = null!; + + [SetUp] + public void SetUp() + { + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(100), + Children = new Drawable[] + { + new Box + { + Colour = Colour4.Red.Opacity(0.5f), + RelativeSizeAxes = Axes.Both, + }, + container = new TestTooltipContainer + { + RelativeSizeAxes = Axes.Both, + Child = new OsuSpriteText + { + Text = "Hover me!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 50) + } + }, + }, + }; + } + + private static readonly string[] test_case_tooltip_string = + [ + "Hello!!", + string.Concat(Enumerable.Repeat("Hello ", 100)), + $"H{new string('e', 500)}llo", + ]; + + [Test] + public void TestTooltipBasic([Values(0, 1, 2)] int index) + { + AddStep("Set tooltip content", () => + { + container.TooltipText = test_case_tooltip_string[index]; + }); + + AddStep("Move to container", () => + { + InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y)); + }); + + OsuTooltipContainer.OsuTooltip? tooltip = null!; + + AddUntilStep("Wait for the tooltip shown", () => + { + tooltip = container.FindClosestParent().ChildrenOfType().FirstOrDefault(); + return tooltip != null && tooltip.Alpha == 1; + }); + + AddAssert("Is tooltip obey 500 width limit", () => tooltip != null && tooltip.Width <= 500); + } + + internal sealed partial class TestTooltipContainer : Container, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } + } +} From de6d8e7eb71f50d8f6708fa4caca1e028b500ec1 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:07:35 +0800 Subject: [PATCH 478/670] Add the custom context menu to handle the key event --- .../Components/EditorContextMenuContainer.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs new file mode 100644 index 0000000000..3207cb0849 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs @@ -0,0 +1,36 @@ +// 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.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Components +{ + public partial class EditorContextMenuContainer : OsuContextMenuContainer, IKeyBindingHandler + { + public override bool ChangeFocusOnClick => true; + + private OsuContextMenu menu = null!; + + protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.Delete: + menu.Close(); + break; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} From 83aeb27c7356f0f8e7b561e79608a089181df5ca Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:08:31 +0800 Subject: [PATCH 479/670] Replace original menu container to the custom one --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1a..96b11b4431 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,7 +213,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { case PlatformAction.Delete: DeleteSelected(); - return true; + // Pass to the `EditorContextMenuContainer` to handle the menu close + return false; } return false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d40db329ec..7eed8809a3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,7 +31,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -43,6 +42,7 @@ using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +319,7 @@ namespace osu.Game.Screens.Edit editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new OsuContextMenuContainer + AddInternal(new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 1ff0c7cb46947597b69e6d0f40fbcab8a39e50f1 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:10:49 +0800 Subject: [PATCH 480/670] Replace original menu container with custom one --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 484af34603..1b5588ef57 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.SkinEditor { RelativeSizeAxes = Axes.Both; - InternalChild = new OsuContextMenuContainer + InternalChild = new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = new GridContainer diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7eed8809a3..d40db329ec 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -42,7 +43,6 @@ using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +319,7 @@ namespace osu.Game.Screens.Edit editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new EditorContextMenuContainer + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 5c5fcd7e7ebdb2ed1aac89d1d31ecb90fdbbd824 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:11:21 +0800 Subject: [PATCH 481/670] Allow key event pass through selection handler --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 96b11b4431..16d11ccd1a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,8 +213,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { case PlatformAction.Delete: DeleteSelected(); - // Pass to the `EditorContextMenuContainer` to handle the menu close - return false; + return true; } return false; From 27d6c4cecb27cfddd87f2d228c4f305cac472777 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:16:14 +0800 Subject: [PATCH 482/670] Implement on beatmap editor --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1a..808e9c71e8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -213,7 +213,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { case PlatformAction.Delete: DeleteSelected(); - return true; + return false; } return false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d40db329ec..4e5abf2f82 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -43,6 +43,7 @@ using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -319,7 +320,7 @@ namespace osu.Game.Screens.Edit editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new OsuContextMenuContainer + AddInternal(new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] From 3cc54667742c51bfa18565f92c400c98c5469354 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:39:06 +0800 Subject: [PATCH 483/670] Refactor the code to follow IoC principle and more flexible --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 184 +++++++++--------- .../Components/EditorContextMenuContainer.cs | 19 +- .../Compose/Components/SelectionHandler.cs | 10 +- osu.Game/Screens/Edit/Editor.cs | 173 ++++++++-------- 4 files changed, 196 insertions(+), 190 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 1b5588ef57..515ab45f55 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -23,7 +23,6 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Dialog; @@ -101,6 +100,12 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private IDialogOverlay? dialogOverlay { get; set; } + [Cached] + public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + }; + public SkinEditor() { } @@ -115,114 +120,111 @@ namespace osu.Game.Overlays.SkinEditor { RelativeSizeAxes = Axes.Both; - InternalChild = new EditorContextMenuContainer + ContextMenuContainer.Child = new GridContainer { RelativeSizeAxes = Axes.Both, - Child = new GridContainer + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - - Content = new[] - { - new Drawable[] + new Container { - new Container + Name = @"Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = MENU_HEIGHT, + Children = new Drawable[] { - Name = @"Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = MENU_HEIGHT, - Children = new Drawable[] + new EditorMenuBar { - new EditorMenuBar + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + Items = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) { - new MenuItem(CommonStrings.MenuBarFile) + Items = new OsuMenuItem[] { - Items = new OsuMenuItem[] - { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), - }, + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), }, - new MenuItem(CommonStrings.MenuBarEdit) - { - Items = new OsuMenuItem[] - { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, - }, - new Drawable[] - { - new SkinEditorSceneLibrary - { - RelativeSizeAxes = Axes.X, - }, - }, - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - componentsSidebar = new EditorSidebar(), - content = new Container - { - Depth = float.MaxValue, - RelativeSizeAxes = Axes.Both, }, - settingsSidebar = new EditorSidebar(), + new MenuItem(CommonStrings.MenuBarEdit) + { + Items = new OsuMenuItem[] + { + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, } + }, + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + componentsSidebar = new EditorSidebar(), + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + settingsSidebar = new EditorSidebar(), } } - }, - } + } + }, } }; + AddInternal(ContextMenuContainer); + clipboardContent = clipboard.Content.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs index 3207cb0849..fa855100d7 100644 --- a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs +++ b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs @@ -1,15 +1,12 @@ // 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.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Components { - public partial class EditorContextMenuContainer : OsuContextMenuContainer, IKeyBindingHandler + public partial class EditorContextMenuContainer : OsuContextMenuContainer { public override bool ChangeFocusOnClick => true; @@ -17,20 +14,14 @@ namespace osu.Game.Screens.Edit.Components protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); - public bool OnPressed(KeyBindingPressEvent e) + public void ShowMenu() { - switch (e.Action) - { - case PlatformAction.Delete: - menu.Close(); - break; - } - - return false; + menu.Show(); } - public void OnReleased(KeyBindingReleaseEvent e) + public void CloseMenu() { + menu.Close(); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 808e9c71e8..45aa50afbf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Input; @@ -59,6 +60,9 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionScaleHandler ScaleHandler { get; private set; } + [Resolved] + private EditorContextMenuContainer editorContextMenuContainer { get; set; } + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -230,7 +234,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Deselect all selected items. /// - protected void DeselectAll() => SelectedItems.Clear(); + protected void DeselectAll() + { + SelectedItems.Clear(); + editorContextMenuContainer.CloseMenu(); + } /// /// Handle a blueprint becoming selected. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 4e5abf2f82..45c3dfe896 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -77,6 +77,12 @@ namespace osu.Game.Screens.Edit /// public const float WAVEFORM_VISUAL_OFFSET = 20; + [Cached] + public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer() + { + RelativeSizeAxes = Axes.Both + }; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -320,109 +326,108 @@ namespace osu.Game.Screens.Edit editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - AddInternal(new EditorContextMenuContainer + ContextMenuContainer.AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - new Container + Name = "Screen container", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 40, Bottom = 50 }, + Child = screenContainer = new Container { - Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 50 }, - Child = screenContainer = new Container - { - RelativeSizeAxes = Axes.Both, - } - }, - new Container + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] + new EditorMenuBar { - new EditorMenuBar + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + MaxHeight = 600, + Items = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - MaxHeight = 600, - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) { - new MenuItem(CommonStrings.MenuBarFile) + Items = createFileMenuItems().ToList() + }, + new MenuItem(CommonStrings.MenuBarEdit) + { + Items = new[] { - Items = createFileMenuItems().ToList() - }, - new MenuItem(CommonStrings.MenuBarEdit) + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + new MenuItem(CommonStrings.MenuBarView) + { + Items = new[] { - Items = new[] + new MenuItem(EditorStrings.Timeline) { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - new MenuItem(CommonStrings.MenuBarView) - { - Items = new[] + Items = + [ + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) + { + State = { BindTarget = editorTimelineShowTimingChanges } + }, + new ToggleMenuItem(EditorStrings.TimelineShowTicks) + { + State = { BindTarget = editorTimelineShowTicks } + }, + ] + }, + new BackgroundDimMenuItem(editorBackgroundDim), + new ToggleMenuItem(EditorStrings.ShowHitMarkers) { - new MenuItem(EditorStrings.Timeline) - { - Items = - [ - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), - new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) - { - State = { BindTarget = editorTimelineShowTimingChanges } - }, - new ToggleMenuItem(EditorStrings.TimelineShowTicks) - { - State = { BindTarget = editorTimelineShowTicks } - }, - ] - }, - new BackgroundDimMenuItem(editorBackgroundDim), - new ToggleMenuItem(EditorStrings.ShowHitMarkers) - { - State = { BindTarget = editorHitMarkers }, - }, - new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) - { - State = { BindTarget = editorAutoSeekOnPlacement }, - }, - new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) - { - State = { BindTarget = editorLimitedDistanceSnap }, - } - } - }, - new MenuItem(EditorStrings.Timing) - { - Items = new MenuItem[] + State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) { - new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + State = { BindTarget = editorAutoSeekOnPlacement }, + }, + new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) + { + State = { BindTarget = editorLimitedDistanceSnap }, } } + }, + new MenuItem(EditorStrings.Timing) + { + Items = new MenuItem[] + { + new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) + } } - }, - screenSwitcher = new EditorScreenSwitcherControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -10, - Current = Mode, - }, + } + }, + screenSwitcher = new EditorScreenSwitcherControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -10, + Current = Mode, }, }, - bottomBar = new BottomBar(), - MutationTracker, - } + }, + bottomBar = new BottomBar(), + MutationTracker, }); + + AddInternal(ContextMenuContainer); + changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); From 5d31171fb0ff3d01b034c655c3995489340236ef Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:43:43 +0800 Subject: [PATCH 484/670] Fix code quality --- osu.Game/Screens/Edit/Editor.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 45c3dfe896..0a944d0627 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,7 +31,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -78,7 +77,7 @@ namespace osu.Game.Screens.Edit public const float WAVEFORM_VISUAL_OFFSET = 20; [Cached] - public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer() + public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer { RelativeSizeAxes = Axes.Both }; From 273bd73a99d79fa3abbe05d14a439a20bccdf869 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:50:57 +0800 Subject: [PATCH 485/670] Fix unit test error --- osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs index 1a1db8eaf7..90545885c7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface private TestTooltipContainer container = null!; [SetUp] - public void SetUp() + public void SetUp() => Schedule(() => { Child = new Container { @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface }, }, }; - } + }); private static readonly string[] test_case_tooltip_string = [ @@ -57,6 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface $"H{new string('e', 500)}llo", ]; + //TODO: o!f issue: https://github.com/ppy/osu-framework/issues/5007 + //Enable after o!f fixed + [Ignore("o!f issue https://github.com/ppy/osu-framework/issues/5007")] [Test] public void TestTooltipBasic([Values(0, 1, 2)] int index) { From 7c83d6a883c017204025cb08aae10f54ed04ce2c Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 19:56:41 +0800 Subject: [PATCH 486/670] Cleanup code --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 45aa50afbf..7335096120 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { case PlatformAction.Delete: DeleteSelected(); - return false; + return true; } return false; From 2145368d17f5740f792c8b721ae3e111823c0f94 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:00:43 +0800 Subject: [PATCH 487/670] Merge `EditorContextMenuContainer` into `OsuContextMenuContainer` --- .../Cursor/OsuContextMenuContainer.cs | 13 ++++++--- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- .../Components/EditorContextMenuContainer.cs | 27 ------------------- .../Compose/Components/BlueprintContainer.cs | 4 +++ .../Compose/Components/SelectionHandler.cs | 4 +-- osu.Game/Screens/Edit/Editor.cs | 4 +-- 6 files changed, 20 insertions(+), 35 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index c5bcfcd2df..85b24cb6a3 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -13,11 +13,18 @@ namespace osu.Game.Graphics.Cursor [Cached] private OsuContextMenuSamples samples = new OsuContextMenuSamples(); - public OsuContextMenuContainer() + private OsuContextMenu menu = null!; + + protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + + public void ShowMenu() { - AddInternal(samples); + menu.Show(); } - protected override Menu CreateMenu() => new OsuContextMenu(true); + public void CloseMenu() + { + menu.Close(); + } } } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 515ab45f55..bb2d93f887 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -32,6 +32,7 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; +using osu.Game.Graphics.Cursor; namespace osu.Game.Overlays.SkinEditor { @@ -101,7 +102,7 @@ namespace osu.Game.Overlays.SkinEditor private IDialogOverlay? dialogOverlay { get; set; } [Cached] - public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer + public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs b/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs deleted file mode 100644 index fa855100d7..0000000000 --- a/osu.Game/Screens/Edit/Components/EditorContextMenuContainer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Edit.Components -{ - public partial class EditorContextMenuContainer : OsuContextMenuContainer - { - public override bool ChangeFocusOnClick => true; - - private OsuContextMenu menu = null!; - - protected override Framework.Graphics.UserInterface.Menu CreateMenu() => menu = new OsuContextMenu(true); - - public void ShowMenu() - { - menu.Show(); - } - - public void CloseMenu() - { - menu.Close(); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index c66be90605..e8228872d9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -46,6 +47,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly BindableList SelectedItems = new BindableList(); + [Resolved(CanBeNull = true)] + private OsuContextMenuContainer contextMenuContainer { get; set; } + protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7335096120..48876278f7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -16,11 +16,11 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Edit; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Input; @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionScaleHandler ScaleHandler { get; private set; } [Resolved] - private EditorContextMenuContainer editorContextMenuContainer { get; set; } + private OsuContextMenuContainer editorContextMenuContainer { get; set; } protected SelectionHandler() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0a944d0627..b1a066afb7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -31,6 +31,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Localisation; @@ -42,7 +43,6 @@ using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit public const float WAVEFORM_VISUAL_OFFSET = 20; [Cached] - public EditorContextMenuContainer ContextMenuContainer { get; private set; } = new EditorContextMenuContainer + public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both }; From 38dacfeaa2e228bf2ab675abefc5e405abd98d10 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:12:09 +0800 Subject: [PATCH 488/670] Fix unit test --- .../Visual/Editing/TestSceneComposeScreen.cs | 2 ++ .../Editing/TestSceneHitObjectComposer.cs | 20 ++++++++++++++----- .../Visual/Editing/TimelineTestScene.cs | 9 +++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 035092ecb7..7405433e73 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -52,6 +53,7 @@ namespace osu.Game.Tests.Visual.Editing (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) }, Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f392841ac7..fac47deec9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -12,6 +13,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -70,13 +72,21 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Create composer", () => { - Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + Child = new DependencyProvidingContainer { - Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { - // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. - FillMode = FillMode.Fit, - FillAspectRatio = 4 / 3f + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) + }, + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) + { + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) + { + // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. + FillMode = FillMode.Fit, + FillAspectRatio = 4 / 3f + } } }; }); diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index cb45ad5a07..14afd3eac1 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -16,6 +16,7 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Storyboards; using osuTK; @@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Editing Composer.Alpha = 0; - Add(new OsuContextMenuContainer + var contextMenuContainer = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -75,7 +76,11 @@ namespace osu.Game.Tests.Visual.Editing Origin = Anchor.Centre, } } - }); + }; + + Dependencies.Cache(contextMenuContainer); + + Add(contextMenuContainer); } [SetUpSteps] From 7cebf4c3d23cf49c11ddf079512154e1d0fc0618 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:18:03 +0800 Subject: [PATCH 489/670] Fix code quality --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 14afd3eac1..2323612e89 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -16,7 +16,6 @@ using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Storyboards; using osuTK; From b32d97b4c055d9b3ba5618673cda22cf039ca4b3 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 21:27:25 +0800 Subject: [PATCH 490/670] Remove decreapted property --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e8228872d9..c66be90605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -47,9 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly BindableList SelectedItems = new BindableList(); - [Resolved(CanBeNull = true)] - private OsuContextMenuContainer contextMenuContainer { get; set; } - protected BlueprintContainer() { RelativeSizeAxes = Axes.Both; From 2720bcf285813e53af3f21cbbb220927c6d4f357 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 22:37:54 +0800 Subject: [PATCH 491/670] Fix ruleset unit test --- .../Editor/TestSceneManiaComposeScreen.cs | 2 ++ .../Editor/TestSceneManiaHitObjectComposer.cs | 11 ++++++++++- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 ++ .../Editor/TestSceneTaikoHitObjectComposer.cs | 11 ++++++++++- .../TestSceneHitObjectComposerDistanceSnapping.cs | 10 ++++++++-- 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 8f623d1fc6..a2f8670774 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; @@ -52,6 +53,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), }, Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index d88f488582..802b2fe843 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; @@ -37,7 +39,14 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }, + }; }); [Test] diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index b70f932913..b57496673b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -16,6 +16,7 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; @@ -79,6 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(composer.DistanceSnapProvider); + dependencies.Cache(new OsuContextMenuContainer()); return dependencies; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 64a29ce866..7c379eb43c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Edit; @@ -22,7 +24,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = new TestComposer { RelativeSizeAxes = Axes.Both }, + }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 12b7dbbf12..ad6aef6302 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -52,9 +54,13 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() => Schedule(() => { - Children = new Drawable[] + Child = new DependencyProvidingContainer { - composer = new TestHitObjectComposer() + CachedDependencies = new (Type, object)[] + { + (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), + }, + Child = composer = new TestHitObjectComposer(), }; BeatDivisor.Value = 1; From 1b25633e4791c46261614f676c98753ea1b5c2ac Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sun, 4 Aug 2024 23:45:42 +0800 Subject: [PATCH 492/670] Fix headless test --- .../Editor/TestSceneManiaHitObjectComposer.cs | 12 ++++-------- .../TestSceneHitObjectComposerDistanceSnapping.cs | 12 ++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 802b2fe843..56ad0a2423 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -33,20 +33,16 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { private TestComposer composer; + [Cached] + public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); + [SetUp] public void Setup() => Schedule(() => { BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }, - }; + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index ad6aef6302..83c660bd4d 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -33,6 +33,9 @@ namespace osu.Game.Tests.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); + protected override Container Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both }; public TestSceneHitObjectComposerDistanceSnapping() @@ -54,14 +57,7 @@ namespace osu.Game.Tests.Editing [SetUp] public void Setup() => Schedule(() => { - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = composer = new TestHitObjectComposer(), - }; + Child = composer = new TestHitObjectComposer(); BeatDivisor.Value = 1; From 2098fb8a9dc07a81102097f789512cd3333c0007 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 00:08:02 +0800 Subject: [PATCH 493/670] Fix code quality --- .../Editor/TestSceneManiaHitObjectComposer.cs | 1 - .../Editing/TestSceneHitObjectComposerDistanceSnapping.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index 56ad0a2423..c2364cce1a 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 83c660bd4d..e5e7d0f8a7 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.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 System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; From b0757a13c20557abc256c9e4937c747dea7d1421 Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Sun, 4 Aug 2024 12:32:08 -0400 Subject: [PATCH 494/670] Add "enter" hint to chatbox placeholder text while in-game --- osu.Game/Localisation/ChatStrings.cs | 5 +++++ .../Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 6b0a6bd8e1..3b1fc6000a 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention"); + /// + /// "press enter to type message..." + /// + public static LocalisableString IngameInputPlaceholder => new TranslatableString(getKey("input.ingameplaceholder"), "press enter to type message..."); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index d003110039..cccab46d98 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; @@ -42,6 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Background.Alpha = 0.2f; TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; + TextBox.PlaceholderText = ChatStrings.IngameInputPlaceholder; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From a5a392e9fc7e9cc121abfe722ab5ad1d8509f7e0 Mon Sep 17 00:00:00 2001 From: AkiraTenchi <34791734+AkiraTenchi@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:48:29 +0200 Subject: [PATCH 495/670] Update FilterQueryParser.cs Add sr as an alias for star rating in the search parameters --- osu.Game/Screens/Select/FilterQueryParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 4e49495f47..40fd289be6 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Select { case "star": case "stars": + case "sr": return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2); case "ar": From f92e2094c166508e7e27a56a4d3a6ed07b52c120 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Aug 2024 12:29:56 +0900 Subject: [PATCH 496/670] Adjust localisation string name + formatting --- osu.Game/Localisation/ChatStrings.cs | 2 +- osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 3b1fc6000a..f7a36d9570 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Localisation /// /// "press enter to type message..." /// - public static LocalisableString IngameInputPlaceholder => new TranslatableString(getKey("input.ingameplaceholder"), "press enter to type message..."); + public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to type message..."); private static string getKey(string key) => $"{prefix}:{key}"; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index cccab46d98..656071ad43 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Background.Alpha = 0.2f; TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; - TextBox.PlaceholderText = ChatStrings.IngameInputPlaceholder; + TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From 20b890570e2edaf39d2b6e68a9feac15db23d8c4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Aug 2024 13:28:42 +0900 Subject: [PATCH 497/670] Replace try-finally with return Try-finally has a small overhead that's unnecessary in this case given how small the code block is. --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 44a2be0024..d90d3d26eb 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -174,14 +174,9 @@ namespace osu.Game.Rulesets.Osu.UI public bool OnPressed(KeyBindingPressEvent e) { - try - { - return BlockNextPress; - } - finally - { - BlockNextPress = false; - } + bool block = BlockNextPress; + BlockNextPress = false; + return block; } public void OnReleased(KeyBindingReleaseEvent e) From 0557b9ab7959deceee5c1ae3ac7bfdf6dc1fe192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 13:20:44 +0900 Subject: [PATCH 498/670] Allow placement deletion with middle mouse This is in addition to Shift + Right-click. I thik middle mouse feels more natural and is a good permanent solution to this issue. Note that this also *allows triggering the context menu from placement mode*. Until now it's done nothing. This may be annoying to users with muscle memory but I want to make the change and harvest feedback. I think showing the context menu is more correct behaviour (although arguably it should return to placement mode on dismiss?). --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +--- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2817e26abd..60b979da59 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -209,9 +209,7 @@ namespace osu.Game.Rulesets.Edit case MouseButtonEvent mouse: // placement blueprints should generally block mouse from reaching underlying components (ie. performing clicks on interface buttons). - // for now, the one exception we want to allow is when using a non-main mouse button when shift is pressed, which is used to trigger object deletion - // while in placement mode. - return mouse.Button == MouseButton.Left || !mouse.ShiftPressed; + return mouse.Button == MouseButton.Left || PlacementActive == PlacementState.Active; default: return false; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 16d11ccd1a..eff6629307 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -263,7 +263,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Whether a selection was performed. internal virtual bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { - if (e.ShiftPressed && e.Button == MouseButton.Right) + if (e.Button == MouseButton.Middle || (e.ShiftPressed && e.Button == MouseButton.Right)) { handleQuickDeletion(blueprint); return true; From 9673985e2cc03247afd25ecf80d58774a3c7bbcf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 14:00:56 +0900 Subject: [PATCH 499/670] Add test coverage of right/middle click behaviours with placement blueprints --- .../Editing/TestScenePlacementBlueprint.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index e9b442f8dd..772a970b5d 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -28,6 +28,51 @@ namespace osu.Game.Tests.Visual.Editing private GlobalActionContainer globalActionContainer => this.ChildrenOfType().Single(); + [Test] + public void TestDeleteUsingMiddleMouse() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with middle mouse", () => InputManager.Click(MouseButton.Middle)); + AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty); + } + + [Test] + public void TestDeleteUsingShiftRightClick() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with right mouse", () => + { + InputManager.PressKey(Key.ShiftLeft); + InputManager.Click(MouseButton.Right); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty); + } + + [Test] + public void TestContextMenu() + { + AddStep("select circle placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType().Single())); + AddStep("place circle", () => InputManager.Click(MouseButton.Left)); + + AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddStep("delete with right mouse", () => + { + InputManager.Click(MouseButton.Right); + }); + AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items); + AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items); + } + [Test] public void TestCommitPlacementViaGlobalAction() { From c0814c2749a467004fe772d199d591d1d8f60d30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 14:24:58 +0900 Subject: [PATCH 500/670] Add test of existing slider placement behaviour for safety --- .../Editing/TestScenePlacementBlueprint.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs index 772a970b5d..8173536ba4 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlacementBlueprint.cs @@ -73,6 +73,29 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items); } + [Test] + [Solo] + public void TestCommitPlacementViaRightClick() + { + Playfield playfield = null!; + + AddStep("select slider placement tool", () => InputManager.Key(Key.Number3)); + AddStep("move mouse to top left of playfield", () => + { + playfield = this.ChildrenOfType().Single(); + var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("begin placement", () => InputManager.Click(MouseButton.Left)); + AddStep("move mouse to bottom right of playfield", () => + { + var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4; + InputManager.MoveMouseTo(location); + }); + AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1)); + } + [Test] public void TestCommitPlacementViaGlobalAction() { From 9c5e29b2c9ec46f0849dab7d186326b5d22573cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 16:58:00 +0900 Subject: [PATCH 501/670] Fix test being disabled for cases which should pass --- .../UserInterface/TestSceneOsuTooltip.cs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs index 90545885c7..49f376c095 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs @@ -21,6 +21,16 @@ namespace osu.Game.Tests.Visual.UserInterface { private TestTooltipContainer container = null!; + private static readonly string[] test_case_tooltip_string = + [ + "Hello!!", + string.Concat(Enumerable.Repeat("Hello ", 100)), + + //TODO: o!f issue: https://github.com/ppy/osu-framework/issues/5007 + //Enable after o!f fixed + // $"H{new string('e', 500)}llo", + ]; + [SetUp] public void SetUp() => Schedule(() => { @@ -50,28 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface }; }); - private static readonly string[] test_case_tooltip_string = - [ - "Hello!!", - string.Concat(Enumerable.Repeat("Hello ", 100)), - $"H{new string('e', 500)}llo", - ]; - - //TODO: o!f issue: https://github.com/ppy/osu-framework/issues/5007 - //Enable after o!f fixed - [Ignore("o!f issue https://github.com/ppy/osu-framework/issues/5007")] [Test] public void TestTooltipBasic([Values(0, 1, 2)] int index) { - AddStep("Set tooltip content", () => - { - container.TooltipText = test_case_tooltip_string[index]; - }); + AddStep("Set tooltip content", () => container.TooltipText = test_case_tooltip_string[index]); - AddStep("Move to container", () => - { - InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y)); - }); + AddStep("Move mouse to container", () => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y))); OsuTooltipContainer.OsuTooltip? tooltip = null!; @@ -81,7 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface return tooltip != null && tooltip.Alpha == 1; }); - AddAssert("Is tooltip obey 500 width limit", () => tooltip != null && tooltip.Width <= 500); + AddAssert("Check tooltip is under width limit", () => tooltip != null && tooltip.Width <= 500); } internal sealed partial class TestTooltipContainer : Container, IHasTooltip From 24a0ead62ee6b1172df605dcbca6978f2031678e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 17:01:00 +0900 Subject: [PATCH 502/670] Make tests actually show what value they are testing --- osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs index 49f376c095..a2e88bfbc9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs @@ -60,10 +60,10 @@ namespace osu.Game.Tests.Visual.UserInterface }; }); - [Test] - public void TestTooltipBasic([Values(0, 1, 2)] int index) + [TestCaseSource(nameof(test_case_tooltip_string))] + public void TestTooltipBasic(string text) { - AddStep("Set tooltip content", () => container.TooltipText = test_case_tooltip_string[index]); + AddStep("Set tooltip content", () => container.TooltipText = text); AddStep("Move mouse to container", () => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y))); From c37f617e1d620e726268f38fce59d4c0cd9dd866 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 17:21:47 +0900 Subject: [PATCH 503/670] Adjust song select info icon size slightly Not going to PR this it's just a minor tweak. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 02682c1851..3b0fdc3e47 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -490,7 +490,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex(@"f7dd55"), Icon = FontAwesome.Regular.Circle, - Size = new Vector2(0.8f) + Size = new Vector2(0.7f) }, statistic.CreateIcon().With(i => { @@ -498,7 +498,7 @@ namespace osu.Game.Screens.Select i.Origin = Anchor.Centre; i.RelativeSizeAxes = Axes.Both; i.Colour = Color4Extensions.FromHex(@"f7dd55"); - i.Size = new Vector2(0.64f); + i.Size = new Vector2(0.6f); }), } }, From 6d385c6510855f292583ac56db6116865833cc4d Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:31:15 +0800 Subject: [PATCH 504/670] Remove the meaningless `OpenMenu` method --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 85b24cb6a3..c9d89a9206 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -17,11 +17,6 @@ namespace osu.Game.Graphics.Cursor protected override Menu CreateMenu() => menu = new OsuContextMenu(true); - public void ShowMenu() - { - menu.Show(); - } - public void CloseMenu() { menu.Close(); From 75c0c6a5f9dd18ced06b4b05dc9288d892c81580 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:32:49 +0800 Subject: [PATCH 505/670] Make the `OsuContextMenu` nullable in `SelectionHandler` --- .../Screens/Edit/Compose/Components/SelectionHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 48876278f7..21cd2e891f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -60,8 +60,8 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionScaleHandler ScaleHandler { get; private set; } - [Resolved] - private OsuContextMenuContainer editorContextMenuContainer { get; set; } + [Resolved(CanBeNull = true)] + protected OsuContextMenuContainer ContextMenuContainer { get; private set; } protected SelectionHandler() { @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void DeselectAll() { SelectedItems.Clear(); - editorContextMenuContainer.CloseMenu(); + ContextMenuContainer?.CloseMenu(); } /// From 3c8d0ce59f93dcc9df88479d147aa9f3eefce581 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:40:31 +0800 Subject: [PATCH 506/670] Revert the unit test changes --- .../Editor/TestSceneManiaComposeScreen.cs | 2 -- .../Editor/TestSceneManiaHitObjectComposer.cs | 4 ---- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 -- .../Editor/TestSceneTaikoHitObjectComposer.cs | 11 +---------- ...stSceneHitObjectComposerDistanceSnapping.cs | 4 ---- .../Visual/Editing/TestSceneComposeScreen.cs | 2 -- .../Editing/TestSceneHitObjectComposer.cs | 18 +++++------------- 7 files changed, 6 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index a2f8670774..8f623d1fc6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; @@ -53,7 +52,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), }, Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs index c2364cce1a..d88f488582 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaHitObjectComposer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; @@ -32,9 +31,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { private TestComposer composer; - [Cached] - public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); - [SetUp] public void Setup() => Schedule(() => { diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index b57496673b..b70f932913 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -16,7 +16,6 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; @@ -80,7 +79,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(composer.DistanceSnapProvider); - dependencies.Cache(new OsuContextMenuContainer()); return dependencies; } diff --git a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs index 7c379eb43c..64a29ce866 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Editor/TestSceneTaikoHitObjectComposer.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Edit; @@ -24,14 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor BeatDivisor.Value = 8; EditorClock.Seek(0); - Child = new DependencyProvidingContainer - { - CachedDependencies = new (Type, object)[] - { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()), - }, - Child = new TestComposer { RelativeSizeAxes = Axes.Both }, - }; + Child = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index e5e7d0f8a7..cf8c3c6ef1 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -32,9 +31,6 @@ namespace osu.Game.Tests.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; - [Cached] - public readonly OsuContextMenuContainer ContextMenuContainer = new OsuContextMenuContainer(); - protected override Container Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both }; public TestSceneHitObjectComposerDistanceSnapping() diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 7405433e73..035092ecb7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; @@ -53,7 +52,6 @@ namespace osu.Game.Tests.Visual.Editing (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) }, Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index fac47deec9..c14ef5aaeb 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -72,21 +72,13 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Create composer", () => { - Child = new DependencyProvidingContainer + Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) { - (typeof(OsuContextMenuContainer), new OsuContextMenuContainer()) - }, - Child = editorBeatmapContainer = new EditorBeatmapContainer(Beatmap.Value) - { - Child = hitObjectComposer = new OsuHitObjectComposer(new OsuRuleset()) - { - // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. - FillMode = FillMode.Fit, - FillAspectRatio = 4 / 3f - } + // force the composer to fully overlap the playfield area by setting a 4:3 aspect ratio. + FillMode = FillMode.Fit, + FillAspectRatio = 4 / 3f } }; }); From 59ff549b4d886e07fadc194c357a7313570f8c1b Mon Sep 17 00:00:00 2001 From: jkh675 Date: Mon, 5 Aug 2024 16:46:56 +0800 Subject: [PATCH 507/670] Remove unused using --- osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index c14ef5aaeb..f392841ac7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -13,7 +12,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; From 251d00939439c5a27a91c1b7508a1fbc057f915c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Mon, 5 Aug 2024 16:08:30 +0300 Subject: [PATCH 508/670] moved conversion formulas to respective classes --- .../Difficulty/OsuDifficultyCalculator.cs | 6 +++--- .../Difficulty/OsuPerformanceCalculator.cs | 7 ++++--- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs | 2 ++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 007cd977e5..e93475ecff 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty flashlightRating *= 0.7; } - double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; - double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; + double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating); + double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating); double baseFlashlightPerformance = 0.0; if (mods.Any(h => h is OsuModFlashlight)) - baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0; + baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating); double basePerformance = Math.Pow( diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 18a4b8be0c..67d88b6b01 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes) { - double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (score.Mods.Any(h => h is OsuModRelax)) return 0.0; - double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty); double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -226,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (!score.Mods.Any(h => h is OsuModFlashlight)) return 0.0; - double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0; + double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty); // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (effectiveMissCount > 0) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 3d6d3f99c1..939641cae9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -42,5 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER; + + public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs index 4a6328010b..c4068ef0d7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/OsuStrainSkill.cs @@ -67,5 +67,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return difficulty * DifficultyMultiplier; } + + public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0; } } From 54a8f5b3064499c134fa17734dddaf629ed3628d Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Mon, 5 Aug 2024 11:06:27 -0400 Subject: [PATCH 509/670] Shorten TranslatableString --- osu.Game/Localisation/ChatStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index f7a36d9570..4661f9a53e 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -27,7 +27,7 @@ namespace osu.Game.Localisation /// /// "press enter to type message..." /// - public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to type message..."); + public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat..."); private static string getKey(string key) => $"{prefix}:{key}"; } From 22ab6f577cae8120a59d4b05e5dea591eba067dc Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 12:37:46 +0800 Subject: [PATCH 510/670] Add back the sample into `OsuContextMenu` --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index c9d89a9206..d15b1c2ee9 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -17,6 +17,11 @@ namespace osu.Game.Graphics.Cursor protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + public OsuContextMenuContainer() + { + AddInternal(samples); + } + public void CloseMenu() { menu.Close(); From cb877b76756a8e6c87f97def38673e43a1048b78 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 13:09:48 +0800 Subject: [PATCH 511/670] Close the menu when selecting other object --- osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 21cd2e891f..63112edf30 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components public SelectionScaleHandler ScaleHandler { get; private set; } [Resolved(CanBeNull = true)] - protected OsuContextMenuContainer ContextMenuContainer { get; private set; } + protected OsuContextMenuContainer? ContextMenuContainer { get; private set; } protected SelectionHandler() { @@ -251,6 +251,8 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectedItems.Add(blueprint.Item); selectedBlueprints.Add(blueprint); + + ContextMenuContainer?.CloseMenu(); } /// From c4572ec265eb03217430d899515ecdacce4fcb46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:17:43 +0900 Subject: [PATCH 512/670] Sanitise font sizes / weights --- osu.Game/Skinning/LegacyKeyCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacyKeyCounter.cs b/osu.Game/Skinning/LegacyKeyCounter.cs index 8a182de9b7..609e21b9ff 100644 --- a/osu.Game/Skinning/LegacyKeyCounter.cs +++ b/osu.Game/Skinning/LegacyKeyCounter.cs @@ -63,7 +63,7 @@ namespace osu.Game.Skinning Origin = Anchor.Centre, Text = trigger.Name, Colour = textColour, - Font = OsuFont.GetFont(size: 20), + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), }, }, } @@ -88,7 +88,7 @@ namespace osu.Game.Skinning keyContainer.ScaleTo(0.75f, transition_duration, Easing.Out); keySprite.Colour = ActiveColour; overlayKeyText.Text = CountPresses.Value.ToString(); - overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.Bold); + overlayKeyText.Font = overlayKeyText.Font.With(weight: FontWeight.SemiBold); } protected override void Deactivate(bool forwardPlayback = true) From b91461e661798731b5cc6a59a4fe5be6365451f5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 15:17:52 +0900 Subject: [PATCH 513/670] Refactor + CI fixes --- osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs | 4 ++-- .../Edit/Compose/Components/SelectionHandler.cs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index d15b1c2ee9..33758c618e 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -15,13 +15,13 @@ namespace osu.Game.Graphics.Cursor private OsuContextMenu menu = null!; - protected override Menu CreateMenu() => menu = new OsuContextMenu(true); - public OsuContextMenuContainer() { AddInternal(samples); } + protected override Menu CreateMenu() => menu = new OsuContextMenu(true); + public void CloseMenu() { menu.Close(); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 63112edf30..d3461038bf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -51,14 +49,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly List> selectedBlueprints; - protected SelectionBox SelectionBox { get; private set; } + protected SelectionBox SelectionBox { get; private set; } = null!; [Resolved(CanBeNull = true)] - protected IEditorChangeHandler ChangeHandler { get; private set; } + protected IEditorChangeHandler? ChangeHandler { get; private set; } - public SelectionRotationHandler RotationHandler { get; private set; } + public SelectionRotationHandler RotationHandler { get; private set; } = null!; - public SelectionScaleHandler ScaleHandler { get; private set; } + public SelectionScaleHandler ScaleHandler { get; private set; } = null!; [Resolved(CanBeNull = true)] protected OsuContextMenuContainer? ContextMenuContainer { get; private set; } From 90395aea13cdbf34c63f330961e004cea2c953e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:43:39 +0900 Subject: [PATCH 514/670] Fix incorrect colour fallback handling Adds a note about `GetConfig` being stupid. --- osu.Game/Skinning/ISkin.cs | 3 +++ osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index fa04dda202..2af1eb8dd8 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -47,6 +47,9 @@ namespace osu.Game.Skinning /// /// Retrieve a configuration value. /// + /// + /// Note that while this returns a bindable value, it is not actually updated. + /// Until the API is fixed, just use the received bindable's immediately. /// The requested configuration value. /// A matching value boxed in an , or null if unavailable. IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 8c652085e4..7e0317851d 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osuTK; +using osuTK.Graphics; namespace osu.Game.Skinning { @@ -53,10 +54,8 @@ namespace osu.Game.Skinning protected override void LoadComplete() { base.LoadComplete(); - source.GetConfig(SkinConfiguration.LegacySetting.InputOverlayText)?.BindValueChanged(v => - { - KeyTextColor = v.NewValue; - }, true); + + KeyTextColor = source.GetConfig(new SkinCustomColourLookup(SkinConfiguration.LegacySetting.InputOverlayText))?.Value ?? Color4.Black; Texture? backgroundTexture = source.GetTexture(@"inputoverlay-background"); From c574551ee0c645eecc0585677ce9b0ec172cee66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:02:36 +0900 Subject: [PATCH 515/670] Simplify caching --- .../Visual/Editing/TimelineTestScene.cs | 8 +- .../Cursor/OsuContextMenuContainer.cs | 1 + osu.Game/Overlays/SkinEditor/SkinEditor.cs | 186 +++++++++--------- osu.Game/Screens/Edit/Editor.cs | 184 ++++++++--------- 4 files changed, 188 insertions(+), 191 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 2323612e89..cb45ad5a07 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing Composer.Alpha = 0; - var contextMenuContainer = new OsuContextMenuContainer + Add(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -75,11 +75,7 @@ namespace osu.Game.Tests.Visual.Editing Origin = Anchor.Centre, } } - }; - - Dependencies.Cache(contextMenuContainer); - - Add(contextMenuContainer); + }); } [SetUpSteps] diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 33758c618e..7b21a413f7 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Cursor { + [Cached(typeof(OsuContextMenuContainer))] public partial class OsuContextMenuContainer : ContextMenuContainer { [Cached] diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index bb2d93f887..6ebc52c6b9 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -101,12 +101,6 @@ namespace osu.Game.Overlays.SkinEditor [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - [Cached] - public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - }; - public SkinEditor() { } @@ -121,110 +115,112 @@ namespace osu.Game.Overlays.SkinEditor { RelativeSizeAxes = Axes.Both; - ContextMenuContainer.Child = new GridContainer + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Child = new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new Container - { - Name = @"Menu container", - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Height = MENU_HEIGHT, - Children = new Drawable[] - { - new EditorMenuBar - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new MenuItem(CommonStrings.MenuBarFile) - { - Items = new OsuMenuItem[] - { - new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), - new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), - new OsuMenuItemSpacer(), - new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), - }, - }, - new MenuItem(CommonStrings.MenuBarEdit) - { - Items = new OsuMenuItem[] - { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - } - }, - headerText = new OsuTextFlowContainer - { - TextAnchor = Anchor.TopRight, - Padding = new MarginPadding(5), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, - }, - }, + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), }, - new Drawable[] + Content = new[] { - new SkinEditorSceneLibrary + new Drawable[] { - RelativeSizeAxes = Axes.X, - }, - }, - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Container { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + Name = @"Menu container", + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Height = MENU_HEIGHT, + Children = new Drawable[] { - componentsSidebar = new EditorSidebar(), - content = new Container + new EditorMenuBar { - Depth = float.MaxValue, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, + Items = new[] + { + new MenuItem(CommonStrings.MenuBarFile) + { + Items = new OsuMenuItem[] + { + new EditorMenuItem(Web.CommonStrings.ButtonsSave, MenuItemType.Standard, () => Save()), + new EditorMenuItem(CommonStrings.Export, MenuItemType.Standard, () => skins.ExportCurrentSkin()) { Action = { Disabled = !RuntimeInfo.IsDesktop } }, + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.RevertToDefault, MenuItemType.Destructive, () => dialogOverlay?.Push(new RevertConfirmDialog(revert))), + new OsuMenuItemSpacer(), + new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, () => skinEditorOverlay?.Hide()), + }, + }, + new MenuItem(CommonStrings.MenuBarEdit) + { + Items = new OsuMenuItem[] + { + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + } }, - settingsSidebar = new EditorSidebar(), + headerText = new OsuTextFlowContainer + { + TextAnchor = Anchor.TopRight, + Padding = new MarginPadding(5), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, + }, + }, + }, + new Drawable[] + { + new SkinEditorSceneLibrary + { + RelativeSizeAxes = Axes.X, + }, + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + componentsSidebar = new EditorSidebar(), + content = new Container + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }, + settingsSidebar = new EditorSidebar(), + } } } - } - }, + }, + } } - }; - - AddInternal(ContextMenuContainer); + }); clipboardContent = clipboard.Content.GetBoundCopy(); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b1a066afb7..71d4693ac6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework; @@ -76,12 +77,6 @@ namespace osu.Game.Screens.Edit /// public const float WAVEFORM_VISUAL_OFFSET = 20; - [Cached] - public OsuContextMenuContainer ContextMenuContainer { get; private set; } = new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both - }; - public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; @@ -165,7 +160,7 @@ namespace osu.Game.Screens.Edit private string lastSavedHash; - private Container screenContainer; + private ScreenContainer screenContainer; [CanBeNull] private readonly EditorLoader loader; @@ -325,108 +320,110 @@ namespace osu.Game.Screens.Edit editorTimelineShowTimingChanges = config.GetBindable(OsuSetting.EditorTimelineShowTimingChanges); editorTimelineShowTicks = config.GetBindable(OsuSetting.EditorTimelineShowTicks); - ContextMenuContainer.AddRange(new Drawable[] + AddInternal(new OsuContextMenuContainer { - new Container + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Name = "Screen container", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 50 }, - Child = screenContainer = new Container + new Container { + Name = "Screen container", RelativeSizeAxes = Axes.Both, - } - }, - new Container - { - Name = "Top bar", - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] - { - new EditorMenuBar + Padding = new MarginPadding { Top = 40, Bottom = 50 }, + Child = screenContainer = new ScreenContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - MaxHeight = 600, - Items = new[] + } + }, + new Container + { + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Children = new Drawable[] + { + new EditorMenuBar { - new MenuItem(CommonStrings.MenuBarFile) + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + MaxHeight = 600, + Items = new[] { - Items = createFileMenuItems().ToList() - }, - new MenuItem(CommonStrings.MenuBarEdit) - { - Items = new[] + new MenuItem(CommonStrings.MenuBarFile) { - undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), - redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), - new OsuMenuItemSpacer(), - cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), - copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), - pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), - cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), - } - }, - new MenuItem(CommonStrings.MenuBarView) - { - Items = new[] + Items = createFileMenuItems().ToList() + }, + new MenuItem(CommonStrings.MenuBarEdit) { - new MenuItem(EditorStrings.Timeline) + Items = new[] { - Items = - [ - new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), - new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) - { - State = { BindTarget = editorTimelineShowTimingChanges } - }, - new ToggleMenuItem(EditorStrings.TimelineShowTicks) - { - State = { BindTarget = editorTimelineShowTicks } - }, - ] - }, - new BackgroundDimMenuItem(editorBackgroundDim), - new ToggleMenuItem(EditorStrings.ShowHitMarkers) + undoMenuItem = new EditorMenuItem(CommonStrings.Undo, MenuItemType.Standard, Undo), + redoMenuItem = new EditorMenuItem(CommonStrings.Redo, MenuItemType.Standard, Redo), + new OsuMenuItemSpacer(), + cutMenuItem = new EditorMenuItem(CommonStrings.Cut, MenuItemType.Standard, Cut), + copyMenuItem = new EditorMenuItem(CommonStrings.Copy, MenuItemType.Standard, Copy), + pasteMenuItem = new EditorMenuItem(CommonStrings.Paste, MenuItemType.Standard, Paste), + cloneMenuItem = new EditorMenuItem(CommonStrings.Clone, MenuItemType.Standard, Clone), + } + }, + new MenuItem(CommonStrings.MenuBarView) + { + Items = new[] { - State = { BindTarget = editorHitMarkers }, - }, - new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) + new MenuItem(EditorStrings.Timeline) + { + Items = + [ + new WaveformOpacityMenuItem(config.GetBindable(OsuSetting.EditorWaveformOpacity)), + new ToggleMenuItem(EditorStrings.TimelineShowTimingChanges) + { + State = { BindTarget = editorTimelineShowTimingChanges } + }, + new ToggleMenuItem(EditorStrings.TimelineShowTicks) + { + State = { BindTarget = editorTimelineShowTicks } + }, + ] + }, + new BackgroundDimMenuItem(editorBackgroundDim), + new ToggleMenuItem(EditorStrings.ShowHitMarkers) + { + State = { BindTarget = editorHitMarkers }, + }, + new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement) + { + State = { BindTarget = editorAutoSeekOnPlacement }, + }, + new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) + { + State = { BindTarget = editorLimitedDistanceSnap }, + } + } + }, + new MenuItem(EditorStrings.Timing) + { + Items = new MenuItem[] { - State = { BindTarget = editorAutoSeekOnPlacement }, - }, - new ToggleMenuItem(EditorStrings.LimitedDistanceSnap) - { - State = { BindTarget = editorLimitedDistanceSnap }, + new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) } } - }, - new MenuItem(EditorStrings.Timing) - { - Items = new MenuItem[] - { - new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime) - } } - } - }, - screenSwitcher = new EditorScreenSwitcherControl - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -10, - Current = Mode, + }, + screenSwitcher = new EditorScreenSwitcherControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -10, + Current = Mode, + }, }, }, - }, - bottomBar = new BottomBar(), - MutationTracker, + bottomBar = new BottomBar(), + MutationTracker, + } }); - AddInternal(ContextMenuContainer); - changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true); changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); @@ -1012,7 +1009,7 @@ namespace osu.Game.Screens.Edit throw new InvalidOperationException("Editor menu bar switched to an unsupported mode"); } - LoadComponentAsync(currentScreen, newScreen => + screenContainer.LoadComponentAsync(currentScreen, newScreen => { if (newScreen == currentScreen) { @@ -1390,5 +1387,12 @@ namespace osu.Game.Screens.Edit { } } + + private partial class ScreenContainer : Container + { + public new Task LoadComponentAsync([NotNull] TLoadable component, Action onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null) + where TLoadable : Drawable + => base.LoadComponentAsync(component, onLoaded, cancellation, scheduler); + } } } From c26a664b849483d9dc12867cf3bcfa2f0fefd829 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:08:42 +0900 Subject: [PATCH 516/670] Use InternalChild directly --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 6ebc52c6b9..a6bb8694ab 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.SkinEditor { RelativeSizeAxes = Axes.Both; - AddInternal(new OsuContextMenuContainer + InternalChild = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Child = new GridContainer @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.SkinEditor }, } } - }); + }; clipboardContent = clipboard.Content.GetBoundCopy(); } From 41d84ea56b1262f356b8c1d657ad8ae211d5ee77 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Aug 2024 16:11:29 +0900 Subject: [PATCH 517/670] Revert all SkinEditor changes (none required) --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index a6bb8694ab..484af34603 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -23,6 +23,7 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Dialog; @@ -32,7 +33,6 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; -using osu.Game.Graphics.Cursor; namespace osu.Game.Overlays.SkinEditor { @@ -127,6 +127,7 @@ namespace osu.Game.Overlays.SkinEditor new Dimension(GridSizeMode.AutoSize), new Dimension(), }, + Content = new[] { new Drawable[] From 8619bbb9435c82018ffc50ab15a61430ae77146a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 15:52:03 +0900 Subject: [PATCH 518/670] Fix legacy key counter's background being visible when intended to be hidden --- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 11 +++--- .../Screens/Play/ArgonKeyCounterDisplay.cs | 3 +- .../Play/HUD/DefaultKeyCounterDisplay.cs | 3 +- .../Screens/Play/HUD/KeyCounterDisplay.cs | 35 ++++++++++++------- osu.Game/Skinning/LegacyKeyCounterDisplay.cs | 2 +- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index f8226eb21d..16b2a54a45 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay // best way to check without exposing. private Drawable hideTarget => hudOverlay.ChildrenOfType().First(); - private Drawable keyCounterFlow => hudOverlay.ChildrenOfType().First().ChildrenOfType>().Single(); + private Drawable keyCounterContent => hudOverlay.ChildrenOfType().First().ChildrenOfType().Skip(1).First(); public TestSceneHUDOverlay() { @@ -79,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); - AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent); + AddAssert("key counter flow is visible", () => keyCounterContent.IsPresent); AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); } @@ -104,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. - AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent); + AddAssert("key counter flow not affected", () => keyCounterContent.IsPresent); } [Test] @@ -150,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); - AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters hidden", () => !keyCounterContent.IsPresent); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0)); - AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent); + AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent); } [Test] diff --git a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs index 44b90fcad0..d5044b9f06 100644 --- a/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/ArgonKeyCounterDisplay.cs @@ -14,11 +14,10 @@ namespace osu.Game.Screens.Play public ArgonKeyCounterDisplay() { - InternalChild = KeyFlow = new FillFlowContainer + Child = KeyFlow = new FillFlowContainer { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Alpha = 0, Spacing = new Vector2(2), }; } diff --git a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs index e0f96d32bc..dfb547453e 100644 --- a/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultKeyCounterDisplay.cs @@ -16,11 +16,10 @@ namespace osu.Game.Screens.Play.HUD public DefaultKeyCounterDisplay() { - InternalChild = KeyFlow = new FillFlowContainer + Child = KeyFlow = new FillFlowContainer { Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, - Alpha = 0, }; } diff --git a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs index 0a5d6b763e..a1e90687a8 100644 --- a/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/HUD/KeyCounterDisplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play.HUD /// /// A flowing display of all gameplay keys. Individual keys can be added using implementations. /// - public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable + public abstract partial class KeyCounterDisplay : Container, ISerialisableDrawable { /// /// Whether the key counter should be visible regardless of the configuration value. @@ -29,25 +29,22 @@ namespace osu.Game.Screens.Play.HUD private readonly IBindableList triggers = new BindableList(); + protected override Container Content { get; } = new Container + { + Alpha = 0, + AutoSizeAxes = Axes.Both, + }; + [Resolved] private InputCountController controller { get; set; } = null!; private const int duration = 100; - protected void UpdateVisibility() + protected KeyCounterDisplay() { - bool visible = AlwaysVisible.Value || ConfigVisibility.Value; - - // Isolate changing visibility of the key counters from fading this component. - KeyFlow.FadeTo(visible ? 1 : 0, duration); - - // Ensure a valid size is immediately obtained even if partially off-screen - // See https://github.com/ppy/osu/issues/14793. - KeyFlow.AlwaysPresent = visible; + AddInternal(Content); } - protected abstract KeyCounter CreateCounter(InputTrigger trigger); - [BackgroundDependencyLoader] private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset) { @@ -70,6 +67,20 @@ namespace osu.Game.Screens.Play.HUD ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); } + protected void UpdateVisibility() + { + bool visible = AlwaysVisible.Value || ConfigVisibility.Value; + + // Isolate changing visibility of the key counters from fading this component. + Content.FadeTo(visible ? 1 : 0, duration); + + // Ensure a valid size is immediately obtained even if partially off-screen + // See https://github.com/ppy/osu/issues/14793. + Content.AlwaysPresent = visible; + } + + protected abstract KeyCounter CreateCounter(InputTrigger trigger); + private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e) { KeyFlow.Clear(); diff --git a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs index 7e0317851d..fdbd3570f5 100644 --- a/osu.Game/Skinning/LegacyKeyCounterDisplay.cs +++ b/osu.Game/Skinning/LegacyKeyCounterDisplay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - AddRangeInternal(new Drawable[] + AddRange(new Drawable[] { backgroundSprite = new Sprite { From aae49d362f5551ceab9ef032e5c1f67a887486e3 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Tue, 6 Aug 2024 16:34:36 +0800 Subject: [PATCH 519/670] Fix unit test code quality --- .../Editor/TestSceneObjectMerging.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs index dfe950c01e..fd711e543c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); moveMouseToHitObject(1); - AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); mergeSelection(); @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); moveMouseToHitObject(1); - AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection")); + AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection")); mergeSelection(); AddAssert("circles not merged", () => circle1 is not null && circle2 is not null && EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2)); @@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); moveMouseToHitObject(1); - AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true); + AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection")); mergeSelection(); From 725dc4de9b80773fe057059eacab9ae244ed21ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 18:17:21 +0900 Subject: [PATCH 520/670] Use transformers for per-skin key counter implementation --- .../Legacy/CatchLegacySkinTransformer.cs | 121 +++++--- .../Legacy/OsuLegacySkinTransformer.cs | 270 ++++++++++-------- osu.Game/Skinning/LegacySkin.cs | 126 +++----- 3 files changed, 274 insertions(+), 243 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index d1ef47cf17..b102ca990c 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Skinning; +using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning.Legacy @@ -28,11 +29,15 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is SkinComponentsContainerLookup containerLookup) + switch (lookup) { - switch (containerLookup.Target) - { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + case SkinComponentsContainerLookup containerLookup: + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + + // Modifications for global components. + if (containerLookup.Ruleset == null) + { var components = base.GetDrawableComponent(lookup) as Container; if (providesComboCounter && components != null) @@ -44,60 +49,84 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy } return components; - } - } + } - if (lookup is CatchSkinComponentLookup catchSkinComponent) - { - switch (catchSkinComponent.Component) - { - case CatchSkinComponents.Fruit: - if (hasPear) - return new LegacyFruitPiece(); + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is Drawable d) + return d; - return null; + // Our own ruleset components default. + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); - case CatchSkinComponents.Banana: - if (GetTexture("fruit-bananas") != null) - return new LegacyBananaPiece(); - - return null; - - case CatchSkinComponents.Droplet: - if (GetTexture("fruit-drop") != null) - return new LegacyDropletPiece(); - - return null; - - case CatchSkinComponents.Catcher: - decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; - - if (version < 2.3m) + if (keyCounter != null) { - if (hasOldStyleCatcherSprite()) - return new LegacyCatcherOld(); + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; - if (hasNewStyleCatcherSprite()) - return new LegacyCatcherNew(); + case CatchSkinComponentLookup catchSkinComponent: + switch (catchSkinComponent.Component) + { + case CatchSkinComponents.Fruit: + if (hasPear) + return new LegacyFruitPiece(); - return null; + return null; - case CatchSkinComponents.CatchComboCounter: - if (providesComboCounter) - return new LegacyCatchComboCounter(); + case CatchSkinComponents.Banana: + if (GetTexture("fruit-bananas") != null) + return new LegacyBananaPiece(); - return null; + return null; - case CatchSkinComponents.HitExplosion: - if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) - return new LegacyHitExplosion(); + case CatchSkinComponents.Droplet: + if (GetTexture("fruit-drop") != null) + return new LegacyDropletPiece(); - return null; + return null; - default: - throw new UnsupportedSkinComponentException(lookup); - } + case CatchSkinComponents.Catcher: + decimal version = GetConfig(SkinConfiguration.LegacySetting.Version)?.Value ?? 1; + + if (version < 2.3m) + { + if (hasOldStyleCatcherSprite()) + return new LegacyCatcherOld(); + } + + if (hasNewStyleCatcherSprite()) + return new LegacyCatcherNew(); + + return null; + + case CatchSkinComponents.CatchComboCounter: + if (providesComboCounter) + return new LegacyCatchComboCounter(); + + return null; + + case CatchSkinComponents.HitExplosion: + if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite()) + return new LegacyHitExplosion(); + + return null; + + default: + throw new UnsupportedSkinComponentException(lookup); + } } return base.GetDrawableComponent(lookup); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index d2ebc68c52..2c2f228fae 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Objects; @@ -41,139 +42,178 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (lookup is OsuSkinComponentLookup osuComponent) + switch (lookup) { - switch (osuComponent.Component) - { - case OsuSkinComponents.FollowPoint: - return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); + case SkinComponentsContainerLookup containerLookup: + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); - case OsuSkinComponents.SliderScorePoint: - return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is Drawable d) + return d; - case OsuSkinComponents.SliderFollowCircle: - var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE); - if (followCircleContent != null) - return new LegacyFollowCircle(followCircleContent); + // Our own ruleset components default. + switch (containerLookup.Target) + { + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); - return null; + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } - case OsuSkinComponents.SliderBall: - if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null) - return new LegacySliderBall(this); + return null; - return null; + case OsuSkinComponentLookup osuComponent: + switch (osuComponent.Component) + { + case OsuSkinComponents.FollowPoint: + return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); - case OsuSkinComponents.SliderBody: - if (hasHitCircle.Value) - return new LegacySliderBody(); + case OsuSkinComponents.SliderScorePoint: + return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); - return null; + case OsuSkinComponents.SliderFollowCircle: + var followCircleContent = this.GetAnimation("sliderfollowcircle", true, true, true, maxSize: MAX_FOLLOW_CIRCLE_AREA_SIZE); + if (followCircleContent != null) + return new LegacyFollowCircle(followCircleContent); - case OsuSkinComponents.SliderTailHitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece("sliderendcircle", false); - - return null; - - case OsuSkinComponents.SliderHeadHitCircle: - if (hasHitCircle.Value) - return new LegacySliderHeadHitCircle(); - - return null; - - case OsuSkinComponents.ReverseArrow: - if (hasHitCircle.Value) - return new LegacyReverseArrow(); - - return null; - - case OsuSkinComponents.HitCircle: - if (hasHitCircle.Value) - return new LegacyMainCirclePiece(); - - return null; - - case OsuSkinComponents.Cursor: - if (GetTexture("cursor") != null) - return new LegacyCursor(this); - - return null; - - case OsuSkinComponents.CursorTrail: - if (GetTexture("cursortrail") != null) - return new LegacyCursorTrail(this); - - return null; - - case OsuSkinComponents.CursorRipple: - if (GetTexture("cursor-ripple") != null) - { - var ripple = this.GetAnimation("cursor-ripple", false, false); - - // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. - // If anyone complains about these not being applied, this can be uncommented. - // - // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, - // so we might be okay. - // - // if (ripple != null) - // { - // ripple.Scale = new Vector2(0.5f); - // ripple.Alpha = 0.2f; - // } - - return ripple; - } - - return null; - - case OsuSkinComponents.CursorParticles: - if (GetTexture("star2") != null) - return new LegacyCursorParticles(); - - return null; - - case OsuSkinComponents.CursorSmoke: - if (GetTexture("cursor-smoke") != null) - return new LegacySmokeSegment(); - - return null; - - case OsuSkinComponents.HitCircleText: - if (!this.HasFont(LegacyFont.HitCircle)) return null; - const float hitcircle_text_scale = 0.8f; - return new LegacySpriteText(LegacyFont.HitCircle) - { - // stable applies a blanket 0.8x scale to hitcircle fonts - Scale = new Vector2(hitcircle_text_scale), - MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale, - }; + case OsuSkinComponents.SliderBall: + if (GetTexture("sliderb") != null || GetTexture("sliderb0") != null) + return new LegacySliderBall(this); - case OsuSkinComponents.SpinnerBody: - bool hasBackground = GetTexture("spinner-background") != null; + return null; - if (GetTexture("spinner-top") != null && !hasBackground) - return new LegacyNewStyleSpinner(); - else if (hasBackground) - return new LegacyOldStyleSpinner(); + case OsuSkinComponents.SliderBody: + if (hasHitCircle.Value) + return new LegacySliderBody(); - return null; + return null; - case OsuSkinComponents.ApproachCircle: - if (GetTexture(@"approachcircle") != null) - return new LegacyApproachCircle(); + case OsuSkinComponents.SliderTailHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderendcircle", false); - return null; + return null; - default: - throw new UnsupportedSkinComponentException(lookup); - } + case OsuSkinComponents.SliderHeadHitCircle: + if (hasHitCircle.Value) + return new LegacySliderHeadHitCircle(); + + return null; + + case OsuSkinComponents.ReverseArrow: + if (hasHitCircle.Value) + return new LegacyReverseArrow(); + + return null; + + case OsuSkinComponents.HitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + + case OsuSkinComponents.Cursor: + if (GetTexture("cursor") != null) + return new LegacyCursor(this); + + return null; + + case OsuSkinComponents.CursorTrail: + if (GetTexture("cursortrail") != null) + return new LegacyCursorTrail(this); + + return null; + + case OsuSkinComponents.CursorRipple: + if (GetTexture("cursor-ripple") != null) + { + var ripple = this.GetAnimation("cursor-ripple", false, false); + + // In stable this element was scaled down to 50% and opacity 20%, but this makes the elements WAY too big and inflexible. + // If anyone complains about these not being applied, this can be uncommented. + // + // But if no one complains I'd rather fix this in lazer. Wiki documentation doesn't mention size, + // so we might be okay. + // + // if (ripple != null) + // { + // ripple.Scale = new Vector2(0.5f); + // ripple.Alpha = 0.2f; + // } + + return ripple; + } + + return null; + + case OsuSkinComponents.CursorParticles: + if (GetTexture("star2") != null) + return new LegacyCursorParticles(); + + return null; + + case OsuSkinComponents.CursorSmoke: + if (GetTexture("cursor-smoke") != null) + return new LegacySmokeSegment(); + + return null; + + case OsuSkinComponents.HitCircleText: + if (!this.HasFont(LegacyFont.HitCircle)) + return null; + + const float hitcircle_text_scale = 0.8f; + return new LegacySpriteText(LegacyFont.HitCircle) + { + // stable applies a blanket 0.8x scale to hitcircle fonts + Scale = new Vector2(hitcircle_text_scale), + MaxSizePerGlyph = OsuHitObject.OBJECT_DIMENSIONS * 2 / hitcircle_text_scale, + }; + + case OsuSkinComponents.SpinnerBody: + bool hasBackground = GetTexture("spinner-background") != null; + + if (GetTexture("spinner-top") != null && !hasBackground) + return new LegacyNewStyleSpinner(); + else if (hasBackground) + return new LegacyOldStyleSpinner(); + + return null; + + case OsuSkinComponents.ApproachCircle: + if (GetTexture(@"approachcircle") != null) + return new LegacyApproachCircle(); + + return null; + + default: + throw new UnsupportedSkinComponentException(lookup); + } + + default: + return base.GetDrawableComponent(lookup); } - - return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 4ca0e3cac0..b1b171eef9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -356,16 +355,57 @@ namespace osu.Game.Skinning switch (lookup) { case SkinComponentsContainerLookup containerLookup: + // Only handle global level defaults for now. + if (containerLookup.Ruleset != null) + return null; switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return createDefaultHUDComponents(containerLookup); + return new DefaultSkinComponentsContainer(container => + { + var score = container.OfType().FirstOrDefault(); + var accuracy = container.OfType().FirstOrDefault(); - default: - return null; + if (score != null && accuracy != null) + { + accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; + } + + var songProgress = container.OfType().FirstOrDefault(); + + if (songProgress != null && accuracy != null) + { + songProgress.Anchor = Anchor.TopRight; + songProgress.Origin = Anchor.CentreRight; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; + songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); + } + + var hitError = container.OfType().FirstOrDefault(); + + if (hitError != null) + { + hitError.Anchor = Anchor.BottomCentre; + hitError.Origin = Anchor.CentreLeft; + hitError.Rotation = -90; + } + }) + { + Children = new Drawable[] + { + new LegacyComboCounter(), + new LegacyScoreCounter(), + new LegacyAccuracyCounter(), + new LegacySongProgress(), + new LegacyHealthDisplay(), + new BarHitErrorMeter(), + } + }; } + return null; + case GameplaySkinComponentLookup resultComponent: // kind of wasteful that we throw this away, but should do for now. @@ -388,84 +428,6 @@ namespace osu.Game.Skinning return null; } - private static DefaultSkinComponentsContainer? createDefaultHUDComponents(SkinComponentsContainerLookup containerLookup) - { - switch (containerLookup.Ruleset?.ShortName) - { - case null: - { - return new DefaultSkinComponentsContainer(container => - { - var score = container.OfType().FirstOrDefault(); - var accuracy = container.OfType().FirstOrDefault(); - - if (score != null && accuracy != null) - { - accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; - } - - var songProgress = container.OfType().FirstOrDefault(); - - if (songProgress != null && accuracy != null) - { - songProgress.Anchor = Anchor.TopRight; - songProgress.Origin = Anchor.CentreRight; - songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 18; - songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); - } - - var hitError = container.OfType().FirstOrDefault(); - - if (hitError != null) - { - hitError.Anchor = Anchor.BottomCentre; - hitError.Origin = Anchor.CentreLeft; - hitError.Rotation = -90; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyScoreCounter(), - new LegacyAccuracyCounter(), - new LegacySongProgress(), - new LegacyHealthDisplay(), - new BarHitErrorMeter(), - } - }; - } - - case @"osu": - case @"fruits": - { - return new DefaultSkinComponentsContainer(container => - { - var keyCounter = container.OfType().FirstOrDefault(); - - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyKeyCounterDisplay(), - } - }; - } - - default: - return null; - } - } - private Texture? getParticleTexture(HitResult result) { switch (result) From f7b45a26defef5a47f3a0ebf6f91acff623661da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Aug 2024 19:25:23 +0900 Subject: [PATCH 521/670] Improve test coverage and segregation --- .../TestSceneModCustomisationPanel.cs | 103 +++++++++++------- 1 file changed, 64 insertions(+), 39 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 16c9c2bc14..1ada5f40ab 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -12,6 +12,7 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface { @@ -27,6 +28,9 @@ namespace osu.Game.Tests.Visual.UserInterface [SetUp] public void SetUp() => Schedule(() => { + SelectedMods.Value = Array.Empty(); + InputManager.MoveMouseTo(Vector2.One); + Child = new Container { RelativeSizeAxes = Axes.Both, @@ -71,66 +75,87 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestHoverExpand() + public void TestHoverDoesNotExpandWhenNoCustomisableMods() { - // Can not expand by hovering when no supported mod - { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + AddStep("hover header", () => InputManager.MoveMouseTo(header)); - AddAssert("not expanded", () => !panel.Expanded); + checkExpanded(false); - AddStep("hover content", () => InputManager.MoveMouseTo(content)); + AddStep("hover content", () => InputManager.MoveMouseTo(content)); - AddAssert("neither expanded", () => !panel.Expanded); + checkExpanded(false); - AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); - } + AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + } + [Test] + public void TestHoverExpandsWithCustomisableMods() + { AddStep("add customisable mod", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; panel.Enabled.Value = true; }); - // Can expand by hovering when supported mod + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); + + AddStep("move to content", () => InputManager.MoveMouseTo(content)); + checkExpanded(true); + + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(false); + + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); + + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(false); + } + + [Test] + public void TestExpandedStatePersistsWhenClicked() + { + AddStep("add customisable mod", () => { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); - AddAssert("expanded", () => panel.Expanded); + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - AddStep("hover content", () => InputManager.MoveMouseTo(content)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(true); - AddAssert("still expanded", () => panel.Expanded); - } + AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One)); + checkExpanded(true); - // Will collapse when mouse left from content + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + } + + [Test] + public void TestHoverExpandsAndCollapsesWhenHeaderClicked() + { + AddStep("add customisable mod", () => { - AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One)); + SelectedMods.Value = new[] { new OsuModDoubleTime() }; + panel.Enabled.Value = true; + }); - AddAssert("not expanded", () => !panel.Expanded); - } + AddStep("hover header", () => InputManager.MoveMouseTo(header)); + checkExpanded(true); - // Will collapse when mouse left from header - { - AddStep("hover header", () => InputManager.MoveMouseTo(header)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkExpanded(false); + } - AddAssert("expanded", () => panel.Expanded); - - AddStep("left from header", () => InputManager.MoveMouseTo(Vector2.One)); - - AddAssert("not expanded", () => !panel.Expanded); - } - - // Not collapse when mouse left if not expanded by hovering - { - AddStep("expand not by hovering", () => panel.Expanded = true); - - AddStep("hover content", () => InputManager.MoveMouseTo(content)); - - AddStep("moust left", () => InputManager.MoveMouseTo(Vector2.One)); - - AddAssert("still expanded", () => panel.Expanded); - } + private void checkExpanded(bool expanded) + { + AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.Expanded, () => Is.EqualTo(expanded)); } } } From 1aea8e911cad4e86d7108fb8067b3ce30df84975 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 01:33:54 +0900 Subject: [PATCH 522/670] Add test coverage of chat mentions --- .../Visual/Online/TestSceneDrawableChannel.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs index dd12ee34ed..6a077708e3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDrawableChannel.cs @@ -33,6 +33,34 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestMention() + { + AddStep("add normal message", () => channel.AddNewMessages( + new Message(1) + { + Sender = new APIUser + { + Id = 2, + Username = "TestUser2" + }, + Content = "Hello how are you today?", + Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 24, TimeSpan.Zero) + })); + + AddStep("add mention", () => channel.AddNewMessages( + new Message(2) + { + Sender = new APIUser + { + Id = 2, + Username = "TestUser2" + }, + Content = $"Hello {API.LocalUser.Value.Username} how are you today?", + Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 25, TimeSpan.Zero) + })); + } + [Test] public void TestDaySeparators() { From a61bf670d8f2f716f1b8df0b02421f8f67aff886 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 01:18:45 +0900 Subject: [PATCH 523/670] Highlight mentions in chat --- osu.Game/Overlays/Chat/ChatLine.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 29c6ec2564..3f8862de36 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osuTK; @@ -104,6 +105,8 @@ namespace osu.Game.Overlays.Chat } } + private bool isMention; + /// /// The colour used to paint the author's username. /// @@ -255,12 +258,21 @@ namespace osu.Game.Overlays.Chat private void styleMessageContent(SpriteText text) { text.Shadow = false; - text.Font = text.Font.With(size: FontSize, italics: Message.IsAction); + text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium); - bool messageHasColour = Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour); - text.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White; + Color4 messageColour = colourProvider?.Content1 ?? Colour4.White; + + if (isMention) + messageColour = colourProvider?.Highlight1 ?? Color4.Orange; + else if (Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour)) + messageColour = Color4Extensions.FromHex(message.Sender.Colour); + + text.Colour = messageColour; } + [Resolved] + private IAPIProvider api { get; set; } = null!; + private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); @@ -280,6 +292,8 @@ namespace osu.Game.Overlays.Chat // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); + isMention = MessageNotifier.CheckContainsUsername(message.DisplayContent, api.LocalUser.Value.Username); + drawableContentFlow.Clear(); drawableContentFlow.AddLinks(message.DisplayContent, message.Links); } From 06ff858256f1dac610e1daece956252d4a281d4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:40:52 +0900 Subject: [PATCH 524/670] Fix `PresentBeatmap` sometimes favouring an already `DeletePending` beatmap --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53b2fd5904..7e4d2ccf39 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -642,10 +642,10 @@ namespace osu.Game Live databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending); if (beatmap is BeatmapSetInfo localBeatmap) - databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending); if (databasedSet == null) { From 5a63c25f4956b042259e77a3a42d48103393201a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:42:34 +0900 Subject: [PATCH 525/670] Fix clicking the beatmap import notification at the daily challenge screen exiting to main menu --- .../DailyChallenge/DailyChallenge.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index da2d9036c5..c1e1142625 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -44,7 +44,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { [Cached(typeof(IPreviewTrackOwner))] - public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -546,5 +546,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (metadataClient.IsNotNull()) metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet; } + + [Resolved] + private OsuGame? game { get; set; } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + if (!this.IsCurrentScreen()) + return; + + // We can only handle the current daily challenge beatmap. + // If the import was for a different beatmap, pass the duty off to global handling. + if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) + { + game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); + } + + // And if we're handling, we don't really have much to do here. + } } } From dccf766ff3ba723e737257a0dabdeadc6496423f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 19:01:14 +0900 Subject: [PATCH 526/670] Remove obsoleted download setting --- osu.Game/Configuration/OsuConfigManager.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index bef1cf2899..86b8ba98c3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -67,12 +67,6 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); -#pragma warning disable CS0618 // Type or member is obsolete - // this default set MUST remain despite the setting being deprecated, because `SetDefault()` calls are implicitly used to declare the type returned for the lookup. - // if this is removed, the setting will be interpreted as a string, and `Migrate()` will fail due to cast failure. - // can be removed 20240618 - SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false); -#pragma warning restore CS0618 // Type or member is obsolete SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled => @@ -244,12 +238,6 @@ namespace osu.Game.Configuration // migrations can be added here using a condition like: // if (combined < 20220103) { performMigration() } - if (combined < 20230918) - { -#pragma warning disable CS0618 // Type or member is obsolete - SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618 -#pragma warning restore CS0618 // Type or member is obsolete - } } public override TrackedSettings CreateTrackedSettings() @@ -424,9 +412,6 @@ namespace osu.Game.Configuration EditorAutoSeekOnPlacement, DiscordRichPresence, - [Obsolete($"Use {nameof(AutomaticallyDownloadMissingBeatmaps)} instead.")] // can be removed 20240318 - AutomaticallyDownloadWhenSpectating, - ShowOnlineExplicitContent, LastProcessedMetadataId, SafeAreaConsiderations, From 227878b67adf0cdb9789e5f1080ad57e9e9cfad6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 19:01:47 +0900 Subject: [PATCH 527/670] Change default for "automatically download beatmaps" to enabled --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 86b8ba98c3..d00856dd80 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -67,7 +67,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.Username, string.Empty); SetDefault(OsuSetting.Token, string.Empty); - SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false); + SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, true); SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled => { From 43f1fe350d2874d3e2082db8b11805a21b313fce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:40:52 +0900 Subject: [PATCH 528/670] Fix `PresentBeatmap` sometimes favouring an already `DeletePending` beatmap --- osu.Game/OsuGame.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53b2fd5904..7e4d2ccf39 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -642,10 +642,10 @@ namespace osu.Game Live databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending); if (beatmap is BeatmapSetInfo localBeatmap) - databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending); if (databasedSet == null) { From 3c05b975a08dacc644f9877f49f58e40bf92668b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 14:42:34 +0900 Subject: [PATCH 529/670] Fix clicking the beatmap import notification at the daily challenge screen exiting to main menu --- .../DailyChallenge/DailyChallenge.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index da2d9036c5..c1e1142625 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -44,7 +44,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { [Cached(typeof(IPreviewTrackOwner))] - public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner + public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap { private readonly Room room; private readonly PlaylistItem playlistItem; @@ -546,5 +546,23 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (metadataClient.IsNotNull()) metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet; } + + [Resolved] + private OsuGame? game { get; set; } + + public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset) + { + if (!this.IsCurrentScreen()) + return; + + // We can only handle the current daily challenge beatmap. + // If the import was for a different beatmap, pass the duty off to global handling. + if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) + { + game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); + } + + // And if we're handling, we don't really have much to do here. + } } } From b081b4771457b901c8cbf31164619c7a211055a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 17:36:01 +0900 Subject: [PATCH 530/670] Add test of daily challenge flow from main menu --- .../Visual/Menus/TestSceneMainMenu.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 57cff38ab0..792b9441fc 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; @@ -23,6 +27,47 @@ namespace osu.Game.Tests.Visual.Menus AddStep("disable return to top on idle", () => Game.ChildrenOfType().Single().ReturnToTopOnIdle = false); } + [Test] + public void TestDailyChallenge() + { + AddStep("set up API", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetRoomRequest getRoomRequest: + if (getRoomRequest.RoomId != 1234) + return false; + + var beatmap = CreateAPIBeatmap(); + beatmap.OnlineID = 1001; + getRoomRequest.TriggerSuccess(new Room + { + RoomID = { Value = 1234 }, + Playlist = + { + new PlaylistItem(beatmap) + }, + EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } + }); + return true; + + default: + return false; + } + }); + + AddStep("beatmap of the day active", () => Game.ChildrenOfType().Single().DailyChallengeUpdated(new DailyChallengeInfo + { + RoomID = 1234, + })); + + AddStep("enter menu", () => InputManager.Key(Key.P)); + AddStep("enter submenu", () => InputManager.Key(Key.P)); + AddStep("enter daily challenge", () => InputManager.Key(Key.D)); + + AddUntilStep("wait for daily challenge screen", () => Game.ScreenStack.CurrentScreen, Is.TypeOf); + } + [Test] public void TestOnlineMenuBannerTrusted() { From 6870311c1e08f55b5a68210b58f81d8731d6b782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 18:07:46 +0900 Subject: [PATCH 531/670] Remove requirement of specifying `animateOnnter` in `BackgroundScreen` ctor --- .../Background/TestSceneBackgroundScreenDefault.cs | 5 ----- osu.Game/Screens/BackgroundScreen.cs | 12 +++++------- osu.Game/Screens/BackgroundScreenStack.cs | 6 +++++- .../Screens/Backgrounds/BackgroundScreenDefault.cs | 5 ----- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- .../Components/OnlinePlayBackgroundScreen.cs | 1 - .../OnlinePlay/OnlinePlayScreenWaveContainer.cs | 1 - 7 files changed, 11 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 37f2ee0b3f..7865d8fef7 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -304,11 +304,6 @@ namespace osu.Game.Tests.Visual.Background { private bool? lastLoadTriggerCausedChange; - public TestBackgroundScreenDefault() - : base(false) - { - } - public override bool Next() { bool didChange = base.Next(); diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index 73af9b1bf2..53f0b39ef7 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -17,13 +17,12 @@ namespace osu.Game.Screens private const float x_movement_amount = 50; - private readonly bool animateOnEnter; - public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; - protected BackgroundScreen(bool animateOnEnter = true) + public bool AnimateEntry { get; set; } = true; + + protected BackgroundScreen() { - this.animateOnEnter = animateOnEnter; Anchor = Anchor.Centre; Origin = Anchor.Centre; } @@ -53,12 +52,11 @@ namespace osu.Game.Screens public override void OnEntering(ScreenTransitionEvent e) { - if (animateOnEnter) + if (AnimateEntry) { this.FadeOut(); - this.MoveToX(x_movement_amount); - this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart); + this.MoveToX(x_movement_amount); this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart); } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs index 99ca383b9f..55cd270581 100644 --- a/osu.Game/Screens/BackgroundScreenStack.cs +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -27,10 +27,14 @@ namespace osu.Game.Screens if (screen == null) return false; - if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) + bool isFirstScreen = CurrentScreen == null; + screen.AnimateEntry = !isFirstScreen; + + if (EqualityComparer.Default.Equals((BackgroundScreen?)CurrentScreen, screen)) return false; base.Push(screen); + return true; } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 090e006671..7be96718bd 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Backgrounds protected virtual bool AllowStoryboardBackground => true; - public BackgroundScreenDefault(bool animateOnEnter = true) - : base(animateOnEnter) - { - } - [BackgroundDependencyLoader] private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config) { diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index ac7dffc241..0dc54b321f 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Menu /// protected bool UsingThemedIntro { get; private set; } - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false) + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault { Colour = Color4.Black }; diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index 014473dfee..ea422f83e3 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -21,7 +21,6 @@ namespace osu.Game.Screens.OnlinePlay.Components private PlaylistItemBackground? background; protected OnlinePlayBackgroundScreen() - : base(false) { AddInternal(new Box { diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs index bfa68d82cd..7898e0845a 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreenWaveContainer.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. -#nullable disable using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; From cfccd74441fcf39e4417e6eeb12dd596c6968d2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Aug 2024 18:07:56 +0900 Subject: [PATCH 532/670] Add daily challenge intro sequence --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 00b9d909a1..dfe5460aee 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Menu OnPlaylists = () => this.Push(new Playlists()), OnDailyChallenge = room => { - this.Push(new DailyChallenge(room)); + this.Push(new DailyChallengeIntro(room)); }, OnExit = () => { diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs new file mode 100644 index 0000000000..2ca5359f5a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.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.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengeIntro : OsuScreen + { + private readonly Room room; + + public DailyChallengeIntro(Room room) + { + this.room = room; + + ValidForResume = false; + } + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "wangs" + } + }; + + Scheduler.AddDelayed(() => + { + this.Push(new DailyChallenge(room)); + }, 2000); + } + } +} From a0615a8f1895cfb7802cd8debe43df858f090a38 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 6 Aug 2024 11:00:04 +0300 Subject: [PATCH 533/670] Frenzi's WIP animation --- .../TestSceneDailyChallengeIntro.cs | 89 +++++++ .../DailyChallenge/DailyChallengeIntro.cs | 251 +++++++++++++++++- 2 files changed, 332 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs diff --git a/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.cs new file mode 100644 index 0000000000..a3541d957e --- /dev/null +++ b/osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallengeIntro.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; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.Metadata; +using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Metadata; +using osu.Game.Tests.Visual.OnlinePlay; +using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest; + +namespace osu.Game.Tests.Visual.DailyChallenge +{ + public partial class TestSceneDailyChallengeIntro : OnlinePlayTestScene + { + [Cached(typeof(MetadataClient))] + private TestMetadataClient metadataClient = new TestMetadataClient(); + + [Cached(typeof(INotificationOverlay))] + private NotificationOverlay notificationOverlay = new NotificationOverlay(); + + [BackgroundDependencyLoader] + private void load() + { + base.Content.Add(notificationOverlay); + base.Content.Add(metadataClient); + } + + [Test] + [Solo] + public void TestDailyChallenge() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(CreateAPIBeatmapSet().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallengeIntro(room))); + } + + [Test] + public void TestNotifications() + { + var room = new Room + { + RoomID = { Value = 1234 }, + Name = { Value = "Daily Challenge: June 4, 2024" }, + Playlist = + { + new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First()) + { + RequiredMods = [new APIMod(new OsuModTraceable())], + AllowedMods = [new APIMod(new OsuModDoubleTime())] + } + }, + EndDate = { Value = DateTimeOffset.Now.AddHours(12) }, + Category = { Value = RoomCategory.DailyChallenge } + }; + + AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); + AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 }); + + Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!; + AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room))); + AddUntilStep("wait for screen", () => screen.IsCurrentScreen()); + AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 2ca5359f5a..e10b587270 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -1,42 +1,277 @@ // 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.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Extensions; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Screens.OnlinePlay.Match; +using osuTK; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeIntro : OsuScreen { private readonly Room room; + private readonly PlaylistItem item; + + private FillFlowContainer introContent = null!; + private Container topPart = null!; + private Container bottomPart = null!; + private Container beatmapBackground = null!; + private Container beatmapTitle = null!; + + private bool beatmapBackgroundLoaded; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); public DailyChallengeIntro(Room room) { this.room = room; + item = room.Playlist.Single(); ValidForResume = false; } - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); + protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { - new OsuSpriteText + introContent = new FillFlowContainer { + Alpha = 0f, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "wangs" + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = topPart = new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = 200f }, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Today's Challenge", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + // Colour = Color4.Black, + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + }, + beatmapBackground = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500f, 150f), + CornerRadius = 20f, + BorderColour = colourProvider.Content2, + BorderThickness = 3f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + } + }, + beatmapTitle = new Container + { + Width = 500f, + Margin = new MarginPadding { Right = 160f * OsuGame.SHEAR }, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = item.Beatmap.GetDisplayString(), + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 24), + }, + } + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = bottomPart = new Container + { + Alpha = 0f, + AlwaysPresent = true, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 210f }, + CornerRadius = 10f, + Masking = true, + Shear = new Vector2(OsuGame.SHEAR, 0f), + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Sunday, July 28th", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + } + } } }; - Scheduler.AddDelayed(() => + LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo) { - this.Push(new DailyChallenge(room)); - }, 2000); + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Scale = new Vector2(1.2f), + Shear = new Vector2(-OsuGame.SHEAR, 0f), + }, c => + { + beatmapBackground.Add(c); + beatmapBackgroundLoaded = true; + updateAnimationState(); + }); + } + + private bool animationBegan; + + public override void OnEntering(ScreenTransitionEvent e) + { + base.OnEntering(e); + this.FadeInFromZero(400, Easing.OutQuint); + updateAnimationState(); + } + + public override void OnSuspending(ScreenTransitionEvent e) + { + this.FadeOut(200, Easing.OutQuint); + base.OnSuspending(e); + } + + private void updateAnimationState() + { + if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) + return; + + if (animationBegan) + return; + + beginAnimation(); + animationBegan = true; + } + + private void beginAnimation() + { + introContent.Show(); + + topPart.MoveToX(-500).MoveToX(0, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); + + bottomPart.MoveToX(500).MoveToX(0, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); + + this.Delay(400).Schedule(() => + { + introContent.AutoSizeDuration = 200; + introContent.AutoSizeEasing = Easing.OutQuint; + }); + + this.Delay(500).Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + + beatmapBackground.FadeOut().Delay(500) + .FadeIn(200, Easing.InQuart); + + beatmapTitle.FadeOut().Delay(500) + .FadeIn(200, Easing.InQuart); + + introContent.Delay(1800).FadeOut(200, Easing.OutQuint) + .OnComplete(_ => + { + if (this.IsCurrentScreen()) + this.Push(new DailyChallenge(room)); + }); + } + + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen + { + private readonly OverlayColourProvider colourProvider; + + public DailyChallengeIntroBackgroundScreen(OverlayColourProvider colourProvider) + : base(null) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new Box + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5.Opacity(0.6f), + }); + } } } } From 083fe32d200043a4363be73948753ef3fe470030 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 18:29:15 +0900 Subject: [PATCH 534/670] Improve feel of animation --- .../DailyChallenge/DailyChallengeIntro.cs | 279 +++++++++++------- 1 file changed, 172 insertions(+), 107 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index e10b587270..b85cdbc2d1 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -8,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Extensions; @@ -15,8 +17,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay.Match; +using osu.Game.Screens.Play.HUD; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.DailyChallenge { @@ -25,11 +30,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private readonly Room room; private readonly PlaylistItem item; - private FillFlowContainer introContent = null!; - private Container topPart = null!; - private Container bottomPart = null!; + private Container introContent = null!; + private Container topTitleDisplay = null!; + private Container bottomDateDisplay = null!; private Container beatmapBackground = null!; - private Container beatmapTitle = null!; + private Box flash = null!; + + private FillFlowContainer beatmapContent = null!; private bool beatmapBackgroundLoaded; @@ -49,79 +56,103 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [BackgroundDependencyLoader] private void load() { + Ruleset ruleset = Ruleset.Value.CreateInstance(); + InternalChildren = new Drawable[] { - introContent = new FillFlowContainer + introContent = new Container { Alpha = 0f, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), + Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { - new Container + beatmapContent = new FillFlowContainer { + AlwaysPresent = true, // so we can get the size ahead of time + Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = topPart = new Container - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = 200f }, - CornerRadius = 10f, - Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Today's Challenge", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - // Colour = Color4.Black, - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, - }, - beatmapBackground = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500f, 150f), - CornerRadius = 20f, - BorderColour = colourProvider.Content2, - BorderThickness = 3f, - Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), + Alpha = 0, + Scale = new Vector2(0.001f), + Spacing = new Vector2(10), Children = new Drawable[] { - new Box + beatmapBackground = new Container { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(500f, 150f), + CornerRadius = 20f, + BorderColour = colourProvider.Content2, + BorderThickness = 3f, + Masking = true, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + flash = new Box + { + Colour = Color4.White, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue, + } + } }, + new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 500f, + AutoSizeAxes = Axes.Y, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new TruncatingSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Text = item.Beatmap.GetDisplayString(), + Padding = new MarginPadding { Vertical = 5f, Horizontal = 5f }, + Font = OsuFont.GetFont(size: 24), + }, + } + }, + new ModFlowDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Current = + { + Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() + }, + } } }, - beatmapTitle = new Container + topTitleDisplay = new Container { - Width = 500f, - Margin = new MarginPadding { Right = 160f * OsuGame.SHEAR }, - AutoSizeAxes = Axes.Y, Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, CornerRadius = 10f, Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { new Box @@ -133,46 +164,38 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = item.Beatmap.GetDisplayString(), + Text = "Today's Challenge", Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), }, } }, - new Container + bottomDateDisplay = new Container { - AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Child = bottomPart = new Container + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] { - Alpha = 0f, - AlwaysPresent = true, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Left = 210f }, - CornerRadius = 10f, - Masking = true, - Shear = new Vector2(OsuGame.SHEAR, 0f), - Children = new Drawable[] + new Box { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Sunday, July 28th", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, - } + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, } } }; @@ -188,12 +211,33 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }, c => { beatmapBackground.Add(c); + beatmapBackgroundLoaded = true; updateAnimationState(); }); } private bool animationBegan; + private bool trackContent; + + private const float initial_v_shift = 32; + private const float final_v_shift = 340; + + protected override void Update() + { + base.Update(); + + if (trackContent) + { + float vShift = initial_v_shift + (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; + + float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); + float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + final_v_shift, 16, Clock.ElapsedFrameTime); + + topTitleDisplay.Position = new Vector2(-xPos, -yPos); + bottomDateDisplay.Position = new Vector2(xPos, yPos); + } + } public override void OnEntering(ScreenTransitionEvent e) { @@ -222,36 +266,57 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void beginAnimation() { - introContent.Show(); + const float v_spacing = 0; - topPart.MoveToX(-500).MoveToX(0, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); - - bottomPart.MoveToX(500).MoveToX(0, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); - - this.Delay(400).Schedule(() => + using (BeginDelayedSequence(200)) { - introContent.AutoSizeDuration = 200; - introContent.AutoSizeEasing = Easing.OutQuint; - }); + introContent.Show(); - this.Delay(500).Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + topTitleDisplay.MoveToOffset(new Vector2(getShearForY(-initial_v_shift), -initial_v_shift)); + bottomDateDisplay.MoveToOffset(new Vector2(getShearForY(initial_v_shift), initial_v_shift)); - beatmapBackground.FadeOut().Delay(500) - .FadeIn(200, Easing.InQuart); + topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - 500) + .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); - beatmapTitle.FadeOut().Delay(500) - .FadeIn(200, Easing.InQuart); + bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + 500) + .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) + .FadeInFromZero(400, Easing.OutQuint); - introContent.Delay(1800).FadeOut(200, Easing.OutQuint) - .OnComplete(_ => + using (BeginDelayedSequence(500)) + { + Schedule(() => trackContent = true); + + beatmapContent + .ScaleTo(1f, 500, Easing.InQuint) + .Then() + .ScaleTo(1.1f, 3000); + + using (BeginDelayedSequence(240)) + { + beatmapContent.FadeInFromZero(280, Easing.InQuad); + + flash + .Delay(400) + .FadeOutFromOne(5000, Easing.OutQuint); + + ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + + using (BeginDelayedSequence(2600)) { - if (this.IsCurrentScreen()) - this.Push(new DailyChallenge(room)); - }); + introContent.FadeOut(200, Easing.OutQuint).OnComplete(_ => + { + if (this.IsCurrentScreen()) + this.Push(new DailyChallenge(room)); + }); + } + } + } + } } + private static float getShearForY(float yPos) => yPos * -OsuGame.SHEAR * 2; + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From e52d80a41b8883683021090343b4e6e8cab3852d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 18:51:44 +0900 Subject: [PATCH 535/670] Add more difficulty information and further tweaks to visuals --- .../DailyChallenge/DailyChallengeIntro.cs | 127 +++++++++++++----- 1 file changed, 97 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index b85cdbc2d1..073ed1c217 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -4,6 +4,7 @@ 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.Containers; @@ -12,7 +13,6 @@ using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; @@ -40,6 +40,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private bool beatmapBackgroundLoaded; + private bool animationBegan; + private bool trackContent; + + private IBindable starDifficulty = null!; + + private const float initial_v_shift = 32; + private const float final_v_shift = 340; + [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -54,10 +62,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load() + private void load(BeatmapDifficultyCache difficultyCache) { + const float horizontal_info_size = 500f; + Ruleset ruleset = Ruleset.Value.CreateInstance(); + StarRatingDisplay starRatingDisplay; + InternalChildren = new Drawable[] { introContent = new Container @@ -85,7 +97,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Size = new Vector2(500f, 150f), + Size = new Vector2(horizontal_info_size, 150f), CornerRadius = 20f, BorderColour = colourProvider.Content2, BorderThickness = 3f, @@ -110,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Width = 500f, + Width = horizontal_info_size, AutoSizeAxes = Axes.Y, CornerRadius = 10f, Masking = true, @@ -121,28 +133,82 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, - new TruncatingSpriteText + new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Text = item.Beatmap.GetDisplayString(), - Padding = new MarginPadding { Vertical = 5f, Horizontal = 5f }, - Font = OsuFont.GetFont(size: 24), + AutoSizeAxes = Axes.Y, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(5f), + Children = new Drawable[] + { + new TruncatingSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + MaxWidth = horizontal_info_size, + Text = item.Beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false), + Padding = new MarginPadding { Horizontal = 5f }, + Font = OsuFont.GetFont(size: 26), + }, + new TruncatingSpriteText + { + Text = $"Difficulty: {item.Beatmap.DifficultyName}", + Font = OsuFont.GetFont(size: 20, italics: true), + MaxWidth = horizontal_info_size, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + new TruncatingSpriteText + { + Text = $"by {item.Beatmap.Metadata.Author.Username}", + Font = OsuFont.GetFont(size: 16, italics: true), + MaxWidth = horizontal_info_size, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + starRatingDisplay = new StarRatingDisplay(default) + { + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Margin = new MarginPadding(5), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } }, } }, - new ModFlowDisplay + new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Current = + Width = horizontal_info_size, + AutoSizeAxes = Axes.Y, + CornerRadius = 10f, + Masking = true, + Children = new Drawable[] { - Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() - }, + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new ModFlowDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Current = + { + Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray() + }, + } + } } } }, @@ -200,6 +266,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } }; + starDifficulty = difficultyCache.GetBindableDifficulty(item.Beatmap); + starDifficulty.BindValueChanged(star => + { + if (star.NewValue != null) + starRatingDisplay.Current.Value = star.NewValue.Value; + }, true); + LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo) { RelativeSizeAxes = Axes.Both, @@ -217,12 +290,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }); } - private bool animationBegan; - private bool trackContent; - - private const float initial_v_shift = 32; - private const float final_v_shift = 340; - protected override void Update() { base.Update(); @@ -248,7 +315,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public override void OnSuspending(ScreenTransitionEvent e) { - this.FadeOut(200, Easing.OutQuint); + this.FadeOut(800, Easing.OutQuint); base.OnSuspending(e); } @@ -290,21 +357,21 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge beatmapContent .ScaleTo(1f, 500, Easing.InQuint) .Then() - .ScaleTo(1.1f, 3000); + .ScaleTo(1.02f, 3000); using (BeginDelayedSequence(240)) { beatmapContent.FadeInFromZero(280, Easing.InQuad); - flash - .Delay(400) - .FadeOutFromOne(5000, Easing.OutQuint); + using (BeginDelayedSequence(300)) + Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); - ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + using (BeginDelayedSequence(400)) + flash.FadeOutFromOne(5000, Easing.OutQuint); using (BeginDelayedSequence(2600)) { - introContent.FadeOut(200, Easing.OutQuint).OnComplete(_ => + Schedule(() => { if (this.IsCurrentScreen()) this.Push(new DailyChallenge(room)); From 775f76f4724f5155efad42860cb1775c8dc279b0 Mon Sep 17 00:00:00 2001 From: kstefanowicz Date: Wed, 7 Aug 2024 07:47:35 -0400 Subject: [PATCH 536/670] Have placeholder text change while focused --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 10 +++++++++- .../OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 7 ++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 3a094cc074..469ba19fd1 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -128,6 +128,9 @@ namespace osu.Game.Online.Chat public partial class ChatTextBox : HistoryTextBox { + public Action Focus; + public Action FocusLost; + protected override bool OnKeyDown(KeyDownEvent e) { // Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other @@ -153,13 +156,18 @@ namespace osu.Game.Online.Chat BackgroundFocused = new Color4(10, 10, 10, 255); } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + Focus?.Invoke(); + } + protected override void OnFocusLost(FocusLostEvent e) { base.OnFocusLost(e); FocusLost?.Invoke(); } - public Action FocusLost; } public partial class StandAloneDrawableChannel : DrawableChannel diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 656071ad43..d1a73457e3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -42,8 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer Background.Alpha = 0.2f; - TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false; TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; + TextBox.Focus = () => TextBox.PlaceholderText = Resources.Localisation.Web.ChatStrings.InputPlaceholder; + TextBox.FocusLost = () => + { + TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder; + expandedFromTextBoxFocus.Value = false; + }; } protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor. From 518c1aa5a0a823a88365ca66a4cce8dae9fdbea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Aug 2024 14:01:30 +0200 Subject: [PATCH 537/670] Remove weird `Expanded` / `ExpandedState` duality --- .../TestSceneFreeModSelectOverlay.cs | 4 ++- .../TestSceneModCustomisationPanel.cs | 15 ++++++---- .../TestSceneModSelectOverlay.cs | 8 +++-- .../Overlays/Mods/ModCustomisationHeader.cs | 4 +-- .../Overlays/Mods/ModCustomisationPanel.cs | 30 ++++++++----------- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 12 ++++---- 6 files changed, 39 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 3097d24595..4316653dde 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -61,7 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() }); AddWaitStep("wait some", 3); - AddAssert("customisation area not expanded", () => !this.ChildrenOfType().Single().Expanded); + AddAssert("customisation area not expanded", + () => this.ChildrenOfType().Single().ExpandedState.Value, + () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs index 1ada5f40ab..c2739e1bbd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModCustomisationPanel.cs @@ -55,22 +55,26 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("set DT", () => { SelectedMods.Value = new[] { new OsuModDoubleTime() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set DA", () => { SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set FL+WU+DA+AD", () => { SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() }; - panel.Enabled.Value = panel.Expanded = true; + panel.Enabled.Value = true; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; }); AddStep("set empty", () => { SelectedMods.Value = Array.Empty(); - panel.Enabled.Value = panel.Expanded = false; + panel.Enabled.Value = false; + panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; }); } @@ -155,7 +159,8 @@ namespace osu.Game.Tests.Visual.UserInterface private void checkExpanded(bool expanded) { - AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.Expanded, () => Is.EqualTo(expanded)); + AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.ExpandedState.Value, + () => expanded ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 0057582755..f21c64f7fe 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -999,7 +999,9 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left)); AddAssert("search still not focused", () => !this.ChildrenOfType().Single().HasFocus); AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("customisation panel closed by click", () => !this.ChildrenOfType().Single().Expanded); + AddAssert("customisation panel closed by click", + () => this.ChildrenOfType().Single().ExpandedState.Value, + () => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); if (textSearchStartsActive) AddAssert("search focused", () => this.ChildrenOfType().Single().HasFocus); @@ -1022,7 +1024,9 @@ namespace osu.Game.Tests.Visual.UserInterface private void assertCustomisationToggleState(bool disabled, bool active) { AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType().Single().Enabled.Value == !disabled); - AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType().Single().Expanded == active); + AddAssert($"customisation panel is {(active ? "" : "not ")}active", + () => modSelectOverlay.ChildrenOfType().Single().ExpandedState.Value, + () => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed)); } private T getSelectedMod() where T : Mod => SelectedMods.Value.OfType().Single(); diff --git a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs index 6d0ca7a769..abd48a0dcb 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationHeader.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationHeader.cs @@ -143,8 +143,8 @@ namespace osu.Game.Overlays.Mods { if (Enabled.Value) { - if (!touchedThisFrame) - panel.UpdateHoverExpansion(ModCustomisationPanelState.ExpandedByHover); + if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed) + panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover; } return base.OnHover(e); diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index a551081a7b..f13ef2725f 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -38,13 +38,7 @@ namespace osu.Game.Overlays.Mods public readonly BindableBool Enabled = new BindableBool(); - public readonly Bindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); - - public bool Expanded - { - get => ExpandedState.Value > ModCustomisationPanelState.Collapsed; - set => ExpandedState.Value = value ? ModCustomisationPanelState.Expanded : ModCustomisationPanelState.Collapsed; - } + public readonly Bindable ExpandedState = new Bindable(); public Bindable> SelectedMods { get; } = new Bindable>(Array.Empty()); @@ -52,9 +46,9 @@ namespace osu.Game.Overlays.Mods // Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded. // These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside - // (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work). - public override bool HandlePositionalInput => Expanded; - public override bool HandleNonPositionalInput => Expanded; + // (handling OnHover or overriding Block{Non}PositionalInput doesn't work). + public override bool HandlePositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed; + public override bool HandleNonPositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed; [BackgroundDependencyLoader] private void load() @@ -140,7 +134,7 @@ namespace osu.Game.Overlays.Mods protected override bool OnClick(ClickEvent e) { - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; return base.OnClick(e); } @@ -153,7 +147,7 @@ namespace osu.Game.Overlays.Mods switch (e.Action) { case GlobalAction.Back: - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; return true; } @@ -168,7 +162,7 @@ namespace osu.Game.Overlays.Mods { content.ClearTransforms(); - if (Expanded) + if (ExpandedState.Value != ModCustomisationPanelState.Collapsed) { content.AutoSizeDuration = 400; content.AutoSizeEasing = Easing.OutQuint; @@ -193,7 +187,7 @@ namespace osu.Game.Overlays.Mods private void updateMods() { - Expanded = false; + ExpandedState.Value = ModCustomisationPanelState.Collapsed; sectionsFlow.Clear(); // Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels). @@ -216,10 +210,10 @@ namespace osu.Game.Overlays.Mods private partial class FocusGrabbingContainer : InputBlockingContainer { - public readonly IBindable ExpandedState = new Bindable(ModCustomisationPanelState.Collapsed); + public readonly Bindable ExpandedState = new Bindable(); - public override bool RequestsFocus => panel.Expanded; - public override bool AcceptsFocus => panel.Expanded; + public override bool RequestsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed; + public override bool AcceptsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed; private readonly ModCustomisationPanel panel; @@ -233,7 +227,7 @@ namespace osu.Game.Overlays.Mods if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover && !ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) { - panel.UpdateHoverExpansion(ModCustomisationPanelState.Collapsed); + ExpandedState.Value = ModCustomisationPanelState.Collapsed; } base.OnHoverLost(e); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e4c5269768..74890df5d9 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -368,18 +368,18 @@ namespace osu.Game.Overlays.Mods customisationPanel.Enabled.Value = true; if (anyModPendingConfiguration) - customisationPanel.Expanded = true; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded; } else { - customisationPanel.Expanded = false; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; customisationPanel.Enabled.Value = false; } } private void updateCustomisationVisualState() { - if (customisationPanel.Expanded) + if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed) { columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint); @@ -544,7 +544,7 @@ namespace osu.Game.Overlays.Mods nonFilteredColumnCount += 1; } - customisationPanel.Expanded = false; + customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed; } #endregion @@ -571,7 +571,7 @@ namespace osu.Game.Overlays.Mods // wherein activating the binding will both change the contents of the search text box and deselect all mods. case GlobalAction.DeselectAllMods: { - if (!SearchTextBox.HasFocus && !customisationPanel.Expanded) + if (!SearchTextBox.HasFocus && customisationPanel.ExpandedState.Value == ModCustomisationPanel.ModCustomisationPanelState.Collapsed) { DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick(); return true; @@ -637,7 +637,7 @@ namespace osu.Game.Overlays.Mods if (e.Repeat || e.Key != Key.Tab) return false; - if (customisationPanel.Expanded) + if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed) return true; // TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`) From f83d43c38b16512f28e479a7a163b2fdc8237427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Aug 2024 14:07:20 +0200 Subject: [PATCH 538/670] Get rid of weird method --- osu.Game/Overlays/Mods/ModCustomisationPanel.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs index f13ef2725f..75cd5d6c91 100644 --- a/osu.Game/Overlays/Mods/ModCustomisationPanel.cs +++ b/osu.Game/Overlays/Mods/ModCustomisationPanel.cs @@ -177,14 +177,6 @@ namespace osu.Game.Overlays.Mods } } - public void UpdateHoverExpansion(ModCustomisationPanelState state) - { - if (state > ModCustomisationPanelState.Collapsed && state <= ExpandedState.Value) - return; - - ExpandedState.Value = state; - } - private void updateMods() { ExpandedState.Value = ModCustomisationPanelState.Collapsed; From cfd7f96e76cbece16f096f0cbe518249b57ce471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Aug 2024 23:29:24 +0900 Subject: [PATCH 539/670] Add missing exit line causing completely incorrect behaviour --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index c1e1142625..e915fdc8ec 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -559,6 +559,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge // If the import was for a different beatmap, pass the duty off to global handling. if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID) { + this.Exit(); game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID); } From 10f704cc416504d24c6a6530c3edffd3534a2082 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Aug 2024 23:50:09 +0900 Subject: [PATCH 540/670] Fix xmldoc --- osu.Game/Localisation/ChatStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs index 4661f9a53e..6841e7d938 100644 --- a/osu.Game/Localisation/ChatStrings.cs +++ b/osu.Game/Localisation/ChatStrings.cs @@ -25,10 +25,10 @@ namespace osu.Game.Localisation public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention"); /// - /// "press enter to type message..." + /// "press enter to chat..." /// public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat..."); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 089ff559d39476a1ef4926af0a470700cce6eeff Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Aug 2024 00:42:31 +0900 Subject: [PATCH 541/670] Fix inspection --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 469ba19fd1..e100b5fe5b 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -167,7 +167,6 @@ namespace osu.Game.Online.Chat base.OnFocusLost(e); FocusLost?.Invoke(); } - } public partial class StandAloneDrawableChannel : DrawableChannel From 85805bffdefc07432624cd17a63ab28d28ee394a Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:03 -0700 Subject: [PATCH 542/670] Remove `Special#` values from `ManiaAction` and remove enum offset --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index a41e72660b..36ccf68d76 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -19,16 +19,8 @@ namespace osu.Game.Rulesets.Mania public enum ManiaAction { - [Description("Special 1")] - Special1 = 1, - - [Description("Special 2")] - Special2, - - // This offsets the start value of normal keys in-case we add more special keys - // above at a later time, without breaking replays/configs. [Description("Key 1")] - Key1 = 10, + Key1, [Description("Key 2")] Key2, From 606b0556d58e8c4b66f8469caa65d01497473584 Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:03 -0700 Subject: [PATCH 543/670] Fix key binding generators --- .../DualStageVariantGenerator.cs | 9 +++----- .../SingleStageVariantGenerator.cs | 4 +--- .../VariantMappingGenerator.cs | 21 +++++++------------ 3 files changed, 11 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs index e9d26b4aa1..6a7634da01 100644 --- a/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs +++ b/osu.Game.Rulesets.Mania/DualStageVariantGenerator.cs @@ -45,18 +45,15 @@ namespace osu.Game.Rulesets.Mania LeftKeys = stage1LeftKeys, RightKeys = stage1RightKeys, SpecialKey = InputKey.V, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1 - }.GenerateKeyBindingsFor(singleStageVariant, out var nextNormal); + }.GenerateKeyBindingsFor(singleStageVariant); var stage2Bindings = new VariantMappingGenerator { LeftKeys = stage2LeftKeys, RightKeys = stage2RightKeys, SpecialKey = InputKey.B, - SpecialAction = ManiaAction.Special2, - NormalActionStart = nextNormal - }.GenerateKeyBindingsFor(singleStageVariant, out _); + ActionStart = (ManiaAction)singleStageVariant, + }.GenerateKeyBindingsFor(singleStageVariant); return stage1Bindings.Concat(stage2Bindings); } diff --git a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs index 44ffeb5ec2..c642da6dc4 100644 --- a/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs +++ b/osu.Game.Rulesets.Mania/SingleStageVariantGenerator.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Mania LeftKeys = leftKeys, RightKeys = rightKeys, SpecialKey = InputKey.Space, - SpecialAction = ManiaAction.Special1, - NormalActionStart = ManiaAction.Key1, - }.GenerateKeyBindingsFor(variant, out _); + }.GenerateKeyBindingsFor(variant); } } diff --git a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs index 2742ee087b..2195c9e1b9 100644 --- a/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs +++ b/osu.Game.Rulesets.Mania/VariantMappingGenerator.cs @@ -26,37 +26,30 @@ namespace osu.Game.Rulesets.Mania public InputKey SpecialKey; /// - /// The at which the normal columns should begin. + /// The at which the columns should begin. /// - public ManiaAction NormalActionStart; - - /// - /// The for the special column. - /// - public ManiaAction SpecialAction; + public ManiaAction ActionStart; /// /// Generates a list of s for a specific number of columns. /// /// The number of columns that need to be bound. - /// The next to use for normal columns. /// The keybindings. - public IEnumerable GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) + public IEnumerable GenerateKeyBindingsFor(int columns) { - ManiaAction currentNormalAction = NormalActionStart; + ManiaAction currentAction = ActionStart; var bindings = new List(); for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) - bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); + bindings.Add(new KeyBinding(LeftKeys[i], currentAction++)); if (columns % 2 == 1) - bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + bindings.Add(new KeyBinding(SpecialKey, currentAction++)); for (int i = 0; i < columns / 2; i++) - bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + bindings.Add(new KeyBinding(RightKeys[i], currentAction++)); - nextNormalAction = currentNormalAction; return bindings; } } From e7f9bba9b57b0e694abe53331e08fe82f339357e Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 544/670] Fix replay frames and auto generator --- .../Replays/ManiaAutoGenerator.cs | 23 +--- .../Replays/ManiaReplayFrame.cs | 101 +----------------- 2 files changed, 6 insertions(+), 118 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index dd3208bd89..a5cc94ea9a 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -17,28 +17,9 @@ namespace osu.Game.Rulesets.Mania.Replays public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; - private readonly ManiaAction[] columnActions; - public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - columnActions = new ManiaAction[Beatmap.TotalColumns]; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - int totalCounter = 0; - - foreach (var stage in Beatmap.Stages) - { - for (int i = 0; i < stage.Columns; i++) - { - if (stage.IsSpecialColumn(i)) - columnActions[totalCounter] = specialAction++; - else - columnActions[totalCounter] = normalAction++; - totalCounter++; - } - } } protected override void GenerateFrames() @@ -57,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays switch (point) { case HitPoint: - actions.Add(columnActions[point.Column]); + actions.Add((ManiaAction)point.Column); break; case ReleasePoint: - actions.Remove(columnActions[point.Column]); + actions.Remove((ManiaAction)point.Column); break; } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index 29249ba474..f80c442025 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,11 +1,9 @@ // 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.Beatmaps; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -27,118 +25,27 @@ namespace osu.Game.Rulesets.Mania.Replays public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame? lastFrame = null) { - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var normalAction = ManiaAction.Key1; - var specialAction = ManiaAction.Special1; - + var action = ManiaAction.Key1; int activeColumns = (int)(legacyFrame.MouseX ?? 0); - int counter = 0; while (activeColumns > 0) { - bool isSpecial = isColumnAtIndexSpecial(maniaBeatmap, counter); - if ((activeColumns & 1) > 0) - Actions.Add(isSpecial ? specialAction : normalAction); + Actions.Add(action); - if (isSpecial) - specialAction++; - else - normalAction++; - - counter++; + action++; activeColumns >>= 1; } } public LegacyReplayFrame ToLegacy(IBeatmap beatmap) { - var maniaBeatmap = (ManiaBeatmap)beatmap; - int keys = 0; foreach (var action in Actions) - { - switch (action) - { - case ManiaAction.Special1: - keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 0); - break; - - case ManiaAction.Special2: - keys |= 1 << getSpecialColumnIndex(maniaBeatmap, 1); - break; - - default: - // the index in lazer, which doesn't include special keys. - int nonSpecialKeyIndex = action - ManiaAction.Key1; - - // the index inclusive of special keys. - int overallIndex = 0; - - // iterate to find the index including special keys. - for (; overallIndex < maniaBeatmap.TotalColumns; overallIndex++) - { - // skip over special columns. - if (isColumnAtIndexSpecial(maniaBeatmap, overallIndex)) - continue; - // found a non-special column to use. - if (nonSpecialKeyIndex == 0) - break; - // found a non-special column but not ours. - nonSpecialKeyIndex--; - } - - keys |= 1 << overallIndex; - break; - } - } + keys |= 1 << (int)action; return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None); } - - /// - /// Find the overall index (across all stages) for a specified special key. - /// - /// The beatmap. - /// The special key offset (0 is S1). - /// The overall index for the special column. - private int getSpecialColumnIndex(ManiaBeatmap maniaBeatmap, int specialOffset) - { - for (int i = 0; i < maniaBeatmap.TotalColumns; i++) - { - if (isColumnAtIndexSpecial(maniaBeatmap, i)) - { - if (specialOffset == 0) - return i; - - specialOffset--; - } - } - - throw new ArgumentException("Special key index is too high.", nameof(specialOffset)); - } - - /// - /// Check whether the column at an overall index (across all stages) is a special column. - /// - /// The beatmap. - /// The overall index to check. - private bool isColumnAtIndexSpecial(ManiaBeatmap beatmap, int index) - { - foreach (var stage in beatmap.Stages) - { - if (index >= stage.Columns) - { - index -= stage.Columns; - continue; - } - - return stage.IsSpecialColumn(index); - } - - throw new ArgumentException("Column index is too high.", nameof(index)); - } } } From 5ad255ecbee0d42e6860192fdcc350bf0e449e0b Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 545/670] Remove special actions from `Stage` constructor --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 5 ++--- osu.Game.Rulesets.Mania/UI/Stage.cs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index b3420c49f3..1f388144bd 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -66,13 +66,12 @@ namespace osu.Game.Rulesets.Mania.UI Content = new[] { new Drawable[stageDefinitions.Count] } }); - var normalColumnAction = ManiaAction.Key1; - var specialColumnAction = ManiaAction.Special1; + var columnAction = ManiaAction.Key1; int firstColumnIndex = 0; for (int i = 0; i < stageDefinitions.Count; i++) { - var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); + var newStage = new Stage(firstColumnIndex, stageDefinitions[i], ref columnAction); playfieldGrid.Content[0][i] = newStage; diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index a4a09c9a82..86f2243561 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI private ISkinSource currentSkin = null!; - public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) + public Stage(int firstColumnIndex, StageDefinition definition, ref ManiaAction columnStartAction) { this.firstColumnIndex = firstColumnIndex; Definition = definition; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.UI { RelativeSizeAxes = Axes.Both, Width = 1, - Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } + Action = { Value = columnStartAction++ } }; topLevelContainer.Add(column.TopLevelContainer.CreateProxy()); From 93e193d7190c56e5995d373fe2d6a2bd290131e3 Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 546/670] Add realm migration to remap key bindings --- osu.Game/Database/RealmAccess.cs | 48 +++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ff76142bcc..ec86d18d4e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -92,8 +92,9 @@ namespace osu.Game.Database /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. + /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1145,6 +1146,51 @@ namespace osu.Game.Database } } + break; + + case 42: + for (int columns = 1; columns <= 10; columns++) + { + remapKeyBindingsForVariant(columns, false); + remapKeyBindingsForVariant(columns, true); + } + + // Replace existing key bindings with new ones reflecting changes to ManiaAction: + // - "Special#" actions are removed and "Key#" actions are inserted in their place. + // - All actions are renumbered to remove the old offsets. + void remapKeyBindingsForVariant(int columns, bool dual) + { + // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaRuleset.cs#L327-L336 + int variant = dual ? 1000 + columns * 2 : columns; + + var oldKeyBindingsQuery = migration.NewRealm + .All() + .Where(kb => kb.RulesetName == @"mania" && kb.Variant == variant); + var oldKeyBindings = oldKeyBindingsQuery.Detach(); + + migration.NewRealm.RemoveRange(oldKeyBindingsQuery); + + // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaInputManager.cs#L22-L31 + int oldNormalAction = 10; // Old Key1 offset + int oldSpecialAction = 1; // Old Special1 offset + + for (int column = 0; column < columns * (dual ? 2 : 1); column++) + { + if (columns % 2 == 1 && column % columns == columns / 2) + remapKeyBinding(oldSpecialAction++, column); + else + remapKeyBinding(oldNormalAction++, column); + } + + void remapKeyBinding(int oldAction, int newAction) + { + var oldKeyBinding = oldKeyBindings.Find(kb => kb.ActionInt == oldAction); + + if (oldKeyBinding != null) + migration.NewRealm.Add(new RealmKeyBinding(newAction, oldKeyBinding.KeyCombination, @"mania", variant)); + } + } + break; } From 48d9bc982fdf4fde520ea2cca35155023fbad34c Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 14:36:04 -0700 Subject: [PATCH 547/670] Fix tests --- .../ManiaLegacyReplayTest.cs | 12 ++++++------ .../Skinning/TestSceneStage.cs | 5 ++--- .../TestSceneAutoGeneration.cs | 8 ++++---- osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs | 4 +--- .../Visual/Gameplay/TestScenePauseInputHandling.cs | 6 +++--- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs index 641631d05e..de036c7b74 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyReplayTest.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Tests { [TestCase(ManiaAction.Key1)] [TestCase(ManiaAction.Key1, ManiaAction.Key2)] - [TestCase(ManiaAction.Special1)] - [TestCase(ManiaAction.Key8)] + [TestCase(ManiaAction.Key5)] + [TestCase(ManiaAction.Key9)] public void TestEncodeDecodeSingleStage(params ManiaAction[] actions) { var beatmap = new ManiaBeatmap(new StageDefinition(9)); @@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase(ManiaAction.Key1)] [TestCase(ManiaAction.Key1, ManiaAction.Key2)] - [TestCase(ManiaAction.Special1)] - [TestCase(ManiaAction.Special2)] - [TestCase(ManiaAction.Special1, ManiaAction.Special2)] - [TestCase(ManiaAction.Special1, ManiaAction.Key5)] + [TestCase(ManiaAction.Key3)] [TestCase(ManiaAction.Key8)] + [TestCase(ManiaAction.Key3, ManiaAction.Key8)] + [TestCase(ManiaAction.Key3, ManiaAction.Key6)] + [TestCase(ManiaAction.Key10)] public void TestEncodeDecodeDualStage(params ManiaAction[] actions) { var beatmap = new ManiaBeatmap(new StageDefinition(5)); diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs index d44a38fdec..091a4cb55b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneStage.cs @@ -14,12 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { SetContents(_ => { - ManiaAction normalAction = ManiaAction.Key1; - ManiaAction specialAction = ManiaAction.Special1; + ManiaAction action = ManiaAction.Key1; return new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4) { - Child = new Stage(0, new StageDefinition(4), ref normalAction, ref specialAction) + Child = new Stage(0, new StageDefinition(4), ref action) }; }); } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index e3846e8213..9a3167b97f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); } [Test] @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); - Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed"); - Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released"); + Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); + Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released"); } [Test] diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index db04142915..195365fb18 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs @@ -131,9 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests private ScrollingTestContainer createStage(ScrollingDirection direction, ManiaAction action) { - var specialAction = ManiaAction.Special1; - - var stage = new Stage(0, new StageDefinition(2), ref action, ref specialAction); + var stage = new Stage(0, new StageDefinition(2), ref action); stages.Add(stage); return new ScrollingTestContainer(direction) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs index 2d03d0cb7c..bc66947ccd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseInputHandling.cs @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); checkKey(() => counter, 0, false); AddStep("press space", () => InputManager.PressKey(Key.Space)); @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); AddStep("press space", () => InputManager.PressKey(Key.Space)); AddStep("pause", () => Player.Pause()); @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay KeyCounter counter = null!; loadPlayer(() => new ManiaRuleset()); - AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Special1)); + AddStep("get key counter", () => counter = this.ChildrenOfType().Single(k => k.Trigger is KeyCounterActionTrigger actionTrigger && actionTrigger.Action == ManiaAction.Key4)); AddStep("press space", () => InputManager.PressKey(Key.Space)); checkKey(() => counter, 1, true); From 8e63c1753677eaa8a5fa58ca90947dac32bf88ee Mon Sep 17 00:00:00 2001 From: clayton Date: Wed, 7 Aug 2024 15:02:53 -0700 Subject: [PATCH 548/670] Apply CodeFactor lint --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ec86d18d4e..cb91d6923b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -1161,7 +1161,7 @@ namespace osu.Game.Database void remapKeyBindingsForVariant(int columns, bool dual) { // https://github.com/ppy/osu/blob/8773c2f7ebc226942d6124eb95c07a83934272ea/osu.Game.Rulesets.Mania/ManiaRuleset.cs#L327-L336 - int variant = dual ? 1000 + columns * 2 : columns; + int variant = dual ? 1000 + (columns * 2) : columns; var oldKeyBindingsQuery = migration.NewRealm .All() From 9bafdeeeff69a2cb122bcdd9bfaa65a71077e978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:17:25 +0900 Subject: [PATCH 549/670] Improve animation --- .../DailyChallenge/DailyChallengeIntro.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 073ed1c217..aa18779d81 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -45,7 +45,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private IBindable starDifficulty = null!; - private const float initial_v_shift = 32; private const float final_v_shift = 340; [Cached] @@ -296,10 +295,10 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (trackContent) { - float vShift = initial_v_shift + (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; + float vShift = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); - float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + final_v_shift, 16, Clock.ElapsedFrameTime); + float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + beatmapContent.Scale.Y * final_v_shift, 16, Clock.ElapsedFrameTime); topTitleDisplay.Position = new Vector2(-xPos, -yPos); bottomDateDisplay.Position = new Vector2(xPos, yPos); @@ -333,34 +332,32 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void beginAnimation() { - const float v_spacing = 0; + const float v_spacing = 5; + const float initial_h_shift = 300; using (BeginDelayedSequence(200)) { introContent.Show(); - topTitleDisplay.MoveToOffset(new Vector2(getShearForY(-initial_v_shift), -initial_v_shift)); - bottomDateDisplay.MoveToOffset(new Vector2(getShearForY(initial_v_shift), initial_v_shift)); - - topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - 500) + topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - initial_h_shift) .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) .FadeInFromZero(400, Easing.OutQuint); - bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + 500) + bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + initial_h_shift) .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) .FadeInFromZero(400, Easing.OutQuint); using (BeginDelayedSequence(500)) { - Schedule(() => trackContent = true); - beatmapContent .ScaleTo(1f, 500, Easing.InQuint) .Then() - .ScaleTo(1.02f, 3000); + .ScaleTo(1.1f, 3000); using (BeginDelayedSequence(240)) { + Schedule(() => trackContent = true); + beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) From f72f5ee7e3484b6f4644b784756962cad750f791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:29:10 +0900 Subject: [PATCH 550/670] More improvements maybe --- .../DailyChallenge/DailyChallengeIntro.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index aa18779d81..af0a015efe 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -45,8 +45,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private IBindable starDifficulty = null!; - private const float final_v_shift = 340; - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); @@ -295,13 +293,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (trackContent) { - float vShift = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2; + float yPos = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2 - 20 * beatmapContent.Scale.Y; + float xPos = getShearForY(yPos) + beatmapContent.Scale.Y * 320; - float yPos = (float)Interpolation.DampContinuously(bottomDateDisplay.Y, vShift, 16, Clock.ElapsedFrameTime); - float xPos = (float)Interpolation.DampContinuously(bottomDateDisplay.X, getShearForY(vShift) + beatmapContent.Scale.Y * final_v_shift, 16, Clock.ElapsedFrameTime); - - topTitleDisplay.Position = new Vector2(-xPos, -yPos); - bottomDateDisplay.Position = new Vector2(xPos, yPos); + topTitleDisplay.Position = new Vector2(-xPos, yPos); + bottomDateDisplay.Position = new Vector2(xPos, -yPos); } } @@ -335,6 +331,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge const float v_spacing = 5; const float initial_h_shift = 300; + introContent.ScaleTo(1.2f, 8000); + using (BeginDelayedSequence(200)) { introContent.Show(); @@ -350,9 +348,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge using (BeginDelayedSequence(500)) { beatmapContent - .ScaleTo(1f, 500, Easing.InQuint) - .Then() - .ScaleTo(1.1f, 3000); + .ScaleTo(1f, 500, Easing.InQuint); using (BeginDelayedSequence(240)) { From 25dddb694a3db52a0bd0ed345c28728786dcc0ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 13:35:55 +0900 Subject: [PATCH 551/670] And then completely change the animation to a new style --- .../DailyChallenge/DailyChallengeIntro.cs | 170 +++++++++--------- 1 file changed, 88 insertions(+), 82 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index af0a015efe..550b7aeda8 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -38,10 +37,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private FillFlowContainer beatmapContent = null!; + private Container titleContainer = null!; + private bool beatmapBackgroundLoaded; private bool animationBegan; - private bool trackContent; private IBindable starDifficulty = null!; @@ -78,6 +78,67 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge Shear = new Vector2(OsuGame.SHEAR, 0f), Children = new Drawable[] { + titleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + topTitleDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + X = -10, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Today's Challenge", + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + bottomDateDisplay = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + CornerRadius = 10f, + Masking = true, + X = 10, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background3, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), + Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, + Shear = new Vector2(-OsuGame.SHEAR, 0f), + Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), + }, + } + }, + } + }, beatmapContent = new FillFlowContainer { AlwaysPresent = true, // so we can get the size ahead of time @@ -209,56 +270,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } }, - topTitleDisplay = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Today's Challenge", - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, - bottomDateDisplay = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - CornerRadius = 10f, - Masking = true, - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(), - Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f }, - Shear = new Vector2(-OsuGame.SHEAR, 0f), - Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate), - }, - } - }, } } }; @@ -287,20 +298,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }); } - protected override void Update() - { - base.Update(); - - if (trackContent) - { - float yPos = (beatmapContent.DrawHeight * beatmapContent.Scale.Y) / 2 - 20 * beatmapContent.Scale.Y; - float xPos = getShearForY(yPos) + beatmapContent.Scale.Y * 320; - - topTitleDisplay.Position = new Vector2(-xPos, yPos); - bottomDateDisplay.Position = new Vector2(xPos, -yPos); - } - } - public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); @@ -328,32 +325,43 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void beginAnimation() { - const float v_spacing = 5; - const float initial_h_shift = 300; - - introContent.ScaleTo(1.2f, 8000); - using (BeginDelayedSequence(200)) { introContent.Show(); - topTitleDisplay.MoveToX(getShearForY(topTitleDisplay.Y) - initial_h_shift) - .MoveToX(getShearForY(topTitleDisplay.Y) - v_spacing, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + const float y_offset_start = 260; + const float y_offset_end = 20; - bottomDateDisplay.MoveToX(getShearForY(bottomDateDisplay.Y) + initial_h_shift) - .MoveToX(getShearForY(bottomDateDisplay.Y) + v_spacing, 300, Easing.OutQuint) - .FadeInFromZero(400, Easing.OutQuint); + topTitleDisplay + .FadeInFromZero(400, Easing.OutQuint); + + topTitleDisplay.MoveToY(-y_offset_start) + .MoveToY(-y_offset_end, 300, Easing.OutQuint) + .Then() + .MoveToY(0, 4000); + + bottomDateDisplay.MoveToY(y_offset_start) + .MoveToY(y_offset_end, 300, Easing.OutQuint) + .Then() + .MoveToY(0, 4000); using (BeginDelayedSequence(500)) { beatmapContent - .ScaleTo(1f, 500, Easing.InQuint); + .ScaleTo(3) + .ScaleTo(1f, 500, Easing.In) + .Then() + .ScaleTo(1.1f, 4000); + + using (BeginDelayedSequence(100)) + { + titleContainer + .ScaleTo(0.4f, 400, Easing.In) + .FadeOut(500, Easing.OutQuint); + } using (BeginDelayedSequence(240)) { - Schedule(() => trackContent = true); - beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) @@ -375,8 +383,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } - private static float getShearForY(float yPos) => yPos * -OsuGame.SHEAR * 2; - private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From 5891780427eb46527a56d4aa6acf695f9678c1f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:15:40 +0900 Subject: [PATCH 552/670] Show initial text a bit longer --- .../Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 550b7aeda8..7570012e43 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge .Then() .MoveToY(0, 4000); - using (BeginDelayedSequence(500)) + using (BeginDelayedSequence(1000)) { beatmapContent .ScaleTo(3) From 278d887ee5ed0c34a9510afa175115ac98e4fd83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:10:14 +0900 Subject: [PATCH 553/670] Fix test failures due to missing room name --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 792b9441fc..613d8347b7 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -43,6 +43,7 @@ namespace osu.Game.Tests.Visual.Menus getRoomRequest.TriggerSuccess(new Room { RoomID = { Value = 1234 }, + Name = { Value = "Aug 8, 2024" }, Playlist = { new PlaylistItem(beatmap) From f91a3e9a350ac3f10f490059ee9ec4bfd4190d55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:41:22 +0900 Subject: [PATCH 554/670] Start playing daily challenge track as part of intro sequence --- .../DailyChallenge/DailyChallenge.cs | 37 ++++++++++--------- .../DailyChallenge/DailyChallengeIntro.cs | 21 ++++++++++- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index b1ff24aa48..6885bc050d 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -389,7 +389,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge base.LoadComplete(); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay); userModsSelectOverlay.SelectedItem.Value = playlistItem; @@ -401,15 +401,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge dailyChallengeInfo.BindValueChanged(dailyChallengeChanged); } - private void trySetDailyChallengeBeatmap() - { - var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID); - Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. - Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID); - - applyLoopingToTrack(); - } - private void onlineStateChanged(ValueChangedEvent state) => Schedule(() => { if (state.NewValue != APIState.Online) @@ -443,7 +434,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge waves.Show(); roomManager.JoinRoom(room); - applyLoopingToTrack(); + startLoopingTrack(this, musicController); metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t => { @@ -465,14 +456,14 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlay.SelectedItem.Value = playlistItem; } public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); - applyLoopingToTrack(); + startLoopingTrack(this, musicController); // re-apply mods as they may have been changed by a child screen // (one known instance of this is showing a replay). updateMods(); @@ -501,17 +492,27 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge return base.OnExiting(e); } - private void applyLoopingToTrack() + public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item) { - if (!this.IsCurrentScreen()) + var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID); + + screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. + screen.Ruleset.Value = rulesets.GetRuleset(item.RulesetID); + + startLoopingTrack(screen, music); + } + + private static void startLoopingTrack(OsuScreen screen, MusicController music) + { + if (!screen.IsCurrentScreen()) return; - var track = Beatmap.Value?.Track; + var track = screen.Beatmap.Value?.Track; if (track != null) { - Beatmap.Value?.PrepareTrackForPreview(true); - musicController.EnsurePlayingSomething(); + screen.Beatmap.Value?.PrepareTrackForPreview(true); + music.EnsurePlayingSomething(); } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7570012e43..83664fdd61 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { public partial class DailyChallengeIntro : OsuScreen { + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override bool? ApplyModTrackAdjustments => true; + private readonly Room room; private readonly PlaylistItem item; @@ -48,6 +52,15 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private MusicController musicController { get; set; } = null!; + public DailyChallengeIntro(Room room) { this.room = room; @@ -365,7 +378,13 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge beatmapContent.FadeInFromZero(280, Easing.InQuad); using (BeginDelayedSequence(300)) - Schedule(() => ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item)); + { + Schedule(() => + { + DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); + ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + }); + } using (BeginDelayedSequence(400)) flash.FadeOutFromOne(5000, Easing.OutQuint); From e95d61d4c239cfe55e48611b30cc42c0a0a6a8e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:01:39 +0900 Subject: [PATCH 555/670] Remove accidental double handling of beatmap availability in `DailyChallenge` --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 6885bc050d..9c8d0ff133 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -455,8 +455,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }); }); - beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); userModsSelectOverlay.SelectedItem.Value = playlistItem; } From 5d66eda9826f7489416963e3e6fd5123f6bfd4cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 14:54:00 +0900 Subject: [PATCH 556/670] Add support for automatically downloading daily challenge during the intro display --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../OnlinePlay/DailyChallenge/DailyChallenge.cs | 3 +++ .../DailyChallenge/DailyChallengeIntro.cs | 13 ++++++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e90b3c703f..cd818941ff 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -563,7 +563,7 @@ namespace osu.Game.Beatmaps remove => workingBeatmapCache.OnInvalidated -= value; } - public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); + public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID && !s.DeletePending)); #endregion diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9c8d0ff133..7cdf546080 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -492,6 +492,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item) { + if (!screen.IsCurrentScreen()) + return; + var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID); screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally. diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 83664fdd61..7254a1f796 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Rooms; @@ -72,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache) + private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config) { const float horizontal_info_size = 500f; @@ -309,11 +310,21 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge beatmapBackgroundLoaded = true; updateAnimationState(); }); + + if (config.Get(OsuSetting.AutomaticallyDownloadMissingBeatmaps)) + { + if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID })) + beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); + } } public override void OnEntering(ScreenTransitionEvent e) { base.OnEntering(e); + + beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); + this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); } From 3f3145e109bb9ccaf4c9c3b9ec6d22952e5398ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 15:01:50 +0900 Subject: [PATCH 557/670] Start playing music during intro if download finishes early --- .../DailyChallenge/DailyChallengeIntro.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 7254a1f796..d00a1ef1e9 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -53,6 +54,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); + [Cached] + private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); + + private bool shouldBePlayingMusic; + [Resolved] private BeatmapManager beatmapManager { get; set; } = null!; @@ -83,6 +89,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge InternalChildren = new Drawable[] { + beatmapAvailabilityTracker, introContent = new Container { Alpha = 0f, @@ -322,8 +329,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { base.OnEntering(e); - beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; - beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true); + beatmapAvailabilityTracker.SelectedItem.Value = item; + beatmapAvailabilityTracker.Availability.BindValueChanged(availability => + { + if (shouldBePlayingMusic && availability.NewValue.State == DownloadState.LocallyAvailable) + DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); + }, true); this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); @@ -392,6 +403,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge { Schedule(() => { + shouldBePlayingMusic = true; DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); }); From 60d383448f22f36e9cc6c95f568cd81bb0ca5bef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:29:54 +0900 Subject: [PATCH 558/670] Avoid making non-ruleset transformers in `Ruleset.CreateSkinTransformer` This didn't make any sense, so let's do it a better way. --- .../Argon/CatchArgonSkinTransformer.cs | 2 +- .../Legacy/CatchLegacySkinTransformer.cs | 2 +- .../Skinning/Argon/OsuArgonSkinTransformer.cs | 2 +- .../Legacy/OsuLegacySkinTransformer.cs | 3 +- .../Argon/TaikoArgonSkinTransformer.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 14 +------ osu.Game/Skinning/ArgonSkin.cs | 21 ++++++++-- osu.Game/Skinning/ArgonSkinTransformer.cs | 40 ------------------- osu.Game/Skinning/LegacySkin.cs | 16 +++++--- osu.Game/Skinning/LegacySkinTransformer.cs | 22 +--------- osu.Game/Skinning/Skin.cs | 5 +-- osu.Game/Skinning/TrianglesSkin.cs | 2 +- .../Skinning/UserConfiguredLayoutContainer.cs | 15 +++++++ 13 files changed, 55 insertions(+), 91 deletions(-) delete mode 100644 osu.Game/Skinning/ArgonSkinTransformer.cs create mode 100644 osu.Game/Skinning/UserConfiguredLayoutContainer.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs index a67945df98..520c2de248 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/CatchArgonSkinTransformer.cs @@ -6,7 +6,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public class CatchArgonSkinTransformer : ArgonSkinTransformer + public class CatchArgonSkinTransformer : SkinTransformer { public CatchArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index abd321ddb1..44fc3ecc07 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return base.GetDrawableComponent(lookup) as Container; // Skin has configuration. - if (base.GetDrawableComponent(lookup) is Drawable d) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; // Our own ruleset components default. diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 2cc36331ae..ec63e1194d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -7,7 +7,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public class OsuArgonSkinTransformer : ArgonSkinTransformer + public class OsuArgonSkinTransformer : SkinTransformer { public OsuArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 2c2f228fae..9a8eaa7d7d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy return base.GetDrawableComponent(lookup); // Skin has configuration. - if (base.GetDrawableComponent(lookup) is Drawable d) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; // Our own ruleset components default. @@ -74,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Children = new Drawable[] { + new LegacyComboCounter(), new LegacyKeyCounterDisplay(), } }; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index 7d38d6c9e5..973b4a91ff 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -7,7 +7,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public class TaikoArgonSkinTransformer : ArgonSkinTransformer + public class TaikoArgonSkinTransformer : SkinTransformer { public TaikoArgonSkinTransformer(ISkin skin) : base(skin) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index ee010e9621..fb0e225c94 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -212,19 +212,7 @@ namespace osu.Game.Rulesets /// The source skin. /// The current beatmap. /// A skin with a transformer applied, or null if no transformation is provided by this ruleset. - public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) - { - switch (skin) - { - case LegacySkin: - return new LegacySkinTransformer(skin); - - case ArgonSkin: - return new ArgonSkinTransformer(skin); - } - - return null; - } + public virtual ISkin? CreateSkinTransformer(ISkin skin, IBeatmap beatmap) => null; protected Ruleset() { diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 707281db31..85abb1edcd 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -7,6 +7,7 @@ using JetBrains.Annotations; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; @@ -93,15 +94,12 @@ namespace osu.Game.Skinning // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; switch (containerLookup.Target) { @@ -114,6 +112,21 @@ namespace osu.Game.Skinning return songSelectComponents; case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + if (containerLookup.Ruleset != null) + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Child = new ArgonComboCounter + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(36, -66), + Scale = new Vector2(1.3f), + }, + }; + } + var mainHUDComponents = new DefaultSkinComponentsContainer(container => { var health = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/ArgonSkinTransformer.cs b/osu.Game/Skinning/ArgonSkinTransformer.cs deleted file mode 100644 index 8ca8f79b41..0000000000 --- a/osu.Game/Skinning/ArgonSkinTransformer.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Skinning -{ - public class ArgonSkinTransformer : SkinTransformer - { - public ArgonSkinTransformer(ISkin skin) - : base(skin) - { - } - - public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) - { - if (lookup is SkinComponentsContainerLookup containerLookup - && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents - && containerLookup.Ruleset != null) - { - return base.GetDrawableComponent(lookup) ?? new Container - { - RelativeSizeAxes = Axes.Both, - Child = new ArgonComboCounter - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Position = new Vector2(36, -66), - Scale = new Vector2(1.3f), - }, - }; - } - - return base.GetDrawableComponent(lookup); - } - } -} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 38bf1631b4..734e80d2ed 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -13,6 +13,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; @@ -349,19 +350,24 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) { case SkinComponentsContainerLookup containerLookup: - // Only handle global level defaults for now. - if (containerLookup.Ruleset != null) - return null; - switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + if (containerLookup.Ruleset != null) + { + return new Container + { + RelativeSizeAxes = Axes.Both, + Child = new LegacyComboCounter(), + }; + } + return new DefaultSkinComponentsContainer(container => { var score = container.OfType().FirstOrDefault(); diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index dbfa52de84..b54e9a1bdf 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -2,42 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Legacy; using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { - public class LegacySkinTransformer : SkinTransformer + public abstract class LegacySkinTransformer : SkinTransformer { /// /// Whether the skin being transformed is able to provide legacy resources for the ruleset. /// public virtual bool IsProvidingLegacyResources => this.HasFont(LegacyFont.Combo); - public LegacySkinTransformer(ISkin skin) + protected LegacySkinTransformer(ISkin skin) : base(skin) { } - public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) - { - if (lookup is SkinComponentsContainerLookup containerLookup - && containerLookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents - && containerLookup.Ruleset != null) - { - return base.GetDrawableComponent(lookup) ?? new Container - { - RelativeSizeAxes = Axes.Both, - Child = new LegacyComboCounter(), - }; - } - - return base.GetDrawableComponent(lookup); - } - public override ISample? GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 5bac5c3d81..226d2fcb89 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -14,7 +14,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -26,7 +25,7 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkin + public abstract partial class Skin : IDisposable, ISkin { private readonly IStorageResourceProvider? resources; @@ -195,7 +194,7 @@ namespace osu.Game.Skinning if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null; if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null; - return new Container + return new UserConfiguredLayoutContainer { RelativeSizeAxes = Axes.Both, ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance()) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 6158d4c7bf..29abb1949f 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -64,7 +64,7 @@ namespace osu.Game.Skinning // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is Drawable c) + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) return c; switch (lookup) diff --git a/osu.Game/Skinning/UserConfiguredLayoutContainer.cs b/osu.Game/Skinning/UserConfiguredLayoutContainer.cs new file mode 100644 index 0000000000..1b5a27b53b --- /dev/null +++ b/osu.Game/Skinning/UserConfiguredLayoutContainer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Skinning +{ + /// + /// This signifies that a call resolved a configuration created + /// by a user in their skin. Generally this should be given priority over any local defaults or overrides. + /// + public partial class UserConfiguredLayoutContainer : Container + { + } +} From 88c5997cb36e9642ca7519e9a5a1e1d21bd6b51e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:36:34 +0900 Subject: [PATCH 559/670] Add back removed xmldoc --- osu.Game/Skinning/LegacySkinTransformer.cs | 3 +++ osu.Game/Skinning/Skin.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index b54e9a1bdf..367e5bae01 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -8,6 +8,9 @@ using static osu.Game.Skinning.SkinConfiguration; namespace osu.Game.Skinning { + /// + /// Transformer used to handle support of legacy features for individual rulesets. + /// public abstract class LegacySkinTransformer : SkinTransformer { /// diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 226d2fcb89..fa09d0c087 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -25,7 +25,7 @@ using osu.Game.Screens.Play.HUD; namespace osu.Game.Skinning { - public abstract partial class Skin : IDisposable, ISkin + public abstract class Skin : IDisposable, ISkin { private readonly IStorageResourceProvider? resources; From 03d543ec99990aa8dbd0d56dbe8c399cda9a6065 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 16:53:22 +0900 Subject: [PATCH 560/670] Fix potential test failure in daily challenge tests See https://github.com/ppy/osu/actions/runs/10296877688/job/28500580680. --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index b1ff24aa48..e7dab3c4fb 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -455,6 +456,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge MultiplayerPlaylistItemStats[] stats = t.GetResultSafely(); var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID); + if (itemStats == null) return; Schedule(() => @@ -462,7 +464,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge breakdown.SetInitialCounts(itemStats.TotalScoreDistribution); totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore); }); - }); + }, TaskContinuationOptions.OnlyOnRanToCompletion); beatmapAvailabilityTracker.SelectedItem.Value = playlistItem; beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true); From e12dba24ae2953ab70df50b55cb726eebc59a33a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Aug 2024 10:49:17 +0300 Subject: [PATCH 561/670] Remove macOS/Xcode version pinning in iOS workflow --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba65cfa33a..4abd55e3f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,9 +121,7 @@ jobs: build-only-ios: name: Build only (iOS) - # `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3. - # TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images) - runs-on: macos-13 + runs-on: macos-latest timeout-minutes: 60 steps: - name: Checkout @@ -137,8 +135,5 @@ jobs: - name: Install .NET Workloads run: dotnet workload install maui-ios - - name: Select Xcode 15.2 - run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - - name: Build run: dotnet build -c Debug osu.iOS From 64271b7bea32663b72ca5f57585dc5880cf28b03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Aug 2024 13:03:00 +0300 Subject: [PATCH 562/670] Remove `iPhone`/`iPhoneSimulator` configurations --- osu.iOS.props | 6 - osu.sln | 336 ++++---------------------------------------------- 2 files changed, 24 insertions(+), 318 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index 196d5594ad..b77aeb6b0f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -16,12 +16,6 @@ false -all - - ios-arm64 - - - iossimulator-x64 - diff --git a/osu.sln b/osu.sln index aeec0843be..829e43fc65 100644 --- a/osu.sln +++ b/osu.sln @@ -98,445 +98,157 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|iPhone = Debug|iPhone - Debug|iPhoneSimulator = Debug|iPhoneSimulator Release|Any CPU = Release|Any CPU - Release|iPhone = Release|iPhone - Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhone.Build.0 = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.ActiveCfg = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.Build.0 = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.ActiveCfg = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhone.Build.0 = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhone.Build.0 = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.ActiveCfg = Release|Any CPU {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.Build.0 = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.ActiveCfg = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhone.Build.0 = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhone.Build.0 = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|Any CPU.Build.0 = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.ActiveCfg = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhone.Build.0 = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3AD63355-D6B1-4365-8D31-5652C989BEF1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhone.Build.0 = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|Any CPU.Build.0 = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.ActiveCfg = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhone.Build.0 = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7E9E9C34-B204-406B-82E2-E01E900699CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhone.Build.0 = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.ActiveCfg = Release|Any CPU {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|Any CPU.Build.0 = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.ActiveCfg = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhone.Build.0 = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B698561F-FB28-46B1-857E-3CA7B92F9D70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhone.Build.0 = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.Build.0 = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.ActiveCfg = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhone.Build.0 = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhone.Build.0 = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.ActiveCfg = Release|Any CPU {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Build.0 = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.ActiveCfg = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhone.Build.0 = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhone.Build.0 = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.ActiveCfg = Release|Any CPU {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.Build.0 = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.ActiveCfg = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhone.Build.0 = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.Build.0 = Release|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.Build.0 = Release|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.Build.0 = Release|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.Build.0 = Release|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.Build.0 = Release|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.Build.0 = Release|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Build.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.ActiveCfg = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhone.Deploy.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Build.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.ActiveCfg = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Build.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhone.Deploy.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Build.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.ActiveCfg = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Build.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhone.Deploy.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Build.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.ActiveCfg = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Build.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhone.Deploy.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Build.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.ActiveCfg = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Build.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhone.Deploy.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Build.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhone.Deploy.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.ActiveCfg = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Build.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhone.Deploy.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhone.Build.0 = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|Any CPU.ActiveCfg = Release|Any CPU {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|Any CPU.Build.0 = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.ActiveCfg = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhone.Build.0 = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhone.Build.0 = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.ActiveCfg = Release|Any CPU {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|Any CPU.Build.0 = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.ActiveCfg = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhone.Build.0 = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {9014CA66-5217-49F6-8C1E-3430FD08EF61}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|Any CPU.Build.0 = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhone.Build.0 = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.ActiveCfg = Release|Any CPU {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|Any CPU.Build.0 = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.ActiveCfg = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhone.Build.0 = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {561DFD5E-5896-40D1-9708-4D692F5BAE66}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhone.Build.0 = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|Any CPU.Build.0 = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.ActiveCfg = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhone.Build.0 = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B325271C-85E7-4DB3-8BBB-B70F242954F8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhone.Build.0 = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.ActiveCfg = Release|Any CPU {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|Any CPU.Build.0 = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.ActiveCfg = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhone.Build.0 = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {4C834F7F-07CA-46C7-8C7B-F10A1B3BC738}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhone.Build.0 = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.ActiveCfg = Release|Any CPU {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|Any CPU.Build.0 = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.ActiveCfg = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhone.Build.0 = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {AD923016-F318-49B7-B08B-89DED6DC2422}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhone.Build.0 = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|Any CPU.Build.0 = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.ActiveCfg = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhone.Build.0 = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B9B92246-02EB-4118-9C6F-85A0D726AA70}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhone.Build.0 = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.ActiveCfg = Release|Any CPU {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|Any CPU.Build.0 = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.ActiveCfg = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhone.Build.0 = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B9022390-8184-4548-9DB1-50EB8878D20A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhone.Build.0 = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.ActiveCfg = Release|Any CPU {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|Any CPU.Build.0 = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.ActiveCfg = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhone.Build.0 = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {1743BF7C-E6AE-4A06-BAD9-166D62894303}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d84d0310e09b00d120b263238c8c00349dc56d21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 12:56:03 +0900 Subject: [PATCH 563/670] Move mute button to master volume circle --- osu.Game/Overlays/Volume/MasterVolumeMeter.cs | 54 +++++++++++++++++++ osu.Game/Overlays/Volume/MuteButton.cs | 12 ++--- osu.Game/Overlays/Volume/VolumeMeter.cs | 16 +++--- osu.Game/Overlays/VolumeOverlay.cs | 53 ++++++++---------- 4 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Overlays/Volume/MasterVolumeMeter.cs diff --git a/osu.Game/Overlays/Volume/MasterVolumeMeter.cs b/osu.Game/Overlays/Volume/MasterVolumeMeter.cs new file mode 100644 index 0000000000..951a6d53b1 --- /dev/null +++ b/osu.Game/Overlays/Volume/MasterVolumeMeter.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Volume +{ + public partial class MasterVolumeMeter : VolumeMeter + { + private MuteButton muteButton = null!; + + public Bindable IsMuted { get; } = new Bindable(); + + private readonly BindableDouble muteAdjustment = new BindableDouble(); + + [Resolved] + private VolumeOverlay volumeOverlay { get; set; } = null!; + + public MasterVolumeMeter(string name, float circleSize, Color4 meterColour) + : base(name, circleSize, meterColour) + { + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + IsMuted.BindValueChanged(muted => + { + if (muted.NewValue) + audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); + else + audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); + }); + + Add(muteButton = new MuteButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + X = CircleSize / 2, + Y = CircleSize * 0.23f, + Current = { BindTarget = IsMuted } + }); + + muteButton.Current.ValueChanged += _ => volumeOverlay.Show(); + } + + public void ToggleMute() => muteButton.Current.Value = !muteButton.Current.Value; + } +} diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 1dc8d754b7..a04d79bd20 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -35,16 +35,16 @@ namespace osu.Game.Overlays.Volume private Color4 hoveredColour, unhoveredColour; - private const float width = 100; - public const float HEIGHT = 35; - public MuteButton() { + const float width = 30; + const float height = 30; + Content.BorderThickness = 3; - Content.CornerRadius = HEIGHT / 2; + Content.CornerRadius = height / 2; Content.CornerExponent = 2; - Size = new Vector2(width, HEIGHT); + Size = new Vector2(width, height); Action = () => Current.Value = !Current.Value; } @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Volume [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredColour = colours.YellowDark; + hoveredColour = colours.PinkLight; Content.BorderColour = unhoveredColour = colours.Gray1; BackgroundColour = colours.Gray1; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index e96cd0fa46..9e0c599386 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -35,8 +35,12 @@ namespace osu.Game.Overlays.Volume private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; + protected static readonly Vector2 LABEL_SIZE = new Vector2(120, 20); + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 }; - private readonly float circleSize; + + protected readonly float CircleSize; + private readonly Color4 meterColour; private readonly string name; @@ -73,7 +77,7 @@ namespace osu.Game.Overlays.Volume public VolumeMeter(string name, float circleSize, Color4 meterColour) { - this.circleSize = circleSize; + CircleSize = circleSize; this.meterColour = meterColour; this.name = name; @@ -101,7 +105,7 @@ namespace osu.Game.Overlays.Volume { new Container { - Size = new Vector2(circleSize), + Size = new Vector2(CircleSize), Children = new Drawable[] { new BufferedContainer @@ -199,7 +203,7 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.Numeric.With(size: 0.16f * circleSize) + Font = OsuFont.Numeric.With(size: 0.16f * CircleSize) }).WithEffect(new GlowEffect { Colour = Color4.Transparent, @@ -209,10 +213,10 @@ namespace osu.Game.Overlays.Volume }, new Container { - Size = new Vector2(120, 20), + Size = LABEL_SIZE, CornerRadius = 10, Masking = true, - Margin = new MarginPadding { Left = circleSize + 10 }, + Margin = new MarginPadding { Left = CircleSize + 10 }, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Children = new Drawable[] diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index 6f9861c703..0d801ff118 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.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.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -20,21 +21,19 @@ using osuTK.Graphics; namespace osu.Game.Overlays { + [Cached] public partial class VolumeOverlay : VisibilityContainer { + public Bindable IsMuted { get; } = new Bindable(); + private const float offset = 10; private VolumeMeter volumeMeterMaster = null!; private VolumeMeter volumeMeterEffect = null!; private VolumeMeter volumeMeterMusic = null!; - private MuteButton muteButton = null!; private SelectionCycleFillFlowContainer volumeMeters = null!; - private readonly BindableDouble muteAdjustment = new BindableDouble(); - - public Bindable IsMuted { get; } = new Bindable(); - [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -49,14 +48,7 @@ namespace osu.Game.Overlays Width = 300, Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0)) }, - muteButton = new MuteButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding(10), - Current = { BindTarget = IsMuted } - }, - volumeMeters = new SelectionCycleFillFlowContainer + new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -64,26 +56,29 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new[] + Children = new Drawable[] { - volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), - volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), - volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), - } - } + volumeMeters = new SelectionCycleFillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Spacing = new Vector2(0, offset), + Children = new[] + { + volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), + volumeMeterMaster = new MasterVolumeMeter("MASTER", 150, colours.PinkDarker) { IsMuted = { BindTarget = IsMuted }, }, + volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker), + } + }, + }, + }, }); volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - - IsMuted.BindValueChanged(muted => - { - if (muted.NewValue) - audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); - else - audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); - }); } protected override void LoadComplete() @@ -92,8 +87,6 @@ namespace osu.Game.Overlays foreach (var volumeMeter in volumeMeters) volumeMeter.Bindable.ValueChanged += _ => Show(); - - muteButton.Current.ValueChanged += _ => Show(); } public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) @@ -130,7 +123,7 @@ namespace osu.Game.Overlays case GlobalAction.ToggleMute: Show(); - muteButton.Current.Value = !muteButton.Current.Value; + volumeMeters.OfType().First().ToggleMute(); return true; } From 0cb3b6a1f8e2668d9593eddaf9d0abe0af6936fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Aug 2024 22:10:26 +0900 Subject: [PATCH 564/670] Add back `TrySetDailyChallengeBeatmap` call for safety --- osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 9b22d368a7..5b341956bb 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -458,6 +458,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge }, TaskContinuationOptions.OnlyOnRanToCompletion); userModsSelectOverlay.SelectedItem.Value = playlistItem; + + TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem); } public override void OnResuming(ScreenTransitionEvent e) From 80c814008f32b348d0cb6322ca4bb2f89607c440 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 00:19:36 +0900 Subject: [PATCH 565/670] Update in line with new changes --- .../Legacy/CatchLegacySkinTransformer.cs | 3 +- .../Argon/ManiaArgonSkinTransformer.cs | 50 ++++++++--------- .../Legacy/LegacyManiaComboCounter.cs | 3 +- .../Legacy/ManiaLegacySkinTransformer.cs | 48 ++++++++-------- .../Legacy/OsuLegacySkinTransformer.cs | 55 ++++++++++--------- .../Visual/Gameplay/TestSceneSkinEditor.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 17 ++++-- 7 files changed, 93 insertions(+), 85 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 44fc3ecc07..df8c04638d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy // Modifications for global components. if (containerLookup.Ruleset == null) - return base.GetDrawableComponent(lookup) as Container; + return base.GetDrawableComponent(lookup); // Skin has configuration. if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index b0a6086f2a..f541cea0f5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public class ManiaArgonSkinTransformer : ArgonSkinTransformer + public class ManiaArgonSkinTransformer : SkinTransformer { private readonly ManiaBeatmap beatmap; @@ -30,32 +29,31 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (lookup) { case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + + // Only handle per ruleset defaults here. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) + return d; + + return new DefaultSkinComponentsContainer(container => { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + var combo = container.ChildrenOfType().FirstOrDefault(); - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = 200; - } - }) - { - new ArgonManiaComboCounter(), - }; - - return rulesetHUDComponents; - } - - break; + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 00619834c8..a51a50c604 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -49,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void updateAnchor() { // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlagFast(Anchor.y1)) + if (!Anchor.HasFlag(Anchor.y1)) { Anchor &= ~(Anchor.y0 | Anchor.y2); Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index c539c239bd..6ac6f6ed18 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; @@ -82,32 +81,31 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy switch (lookup) { case SkinComponentsContainerLookup containerLookup: - switch (containerLookup.Target) + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + + // Modifications for global components. + if (containerLookup.Ruleset == null) + return base.GetDrawableComponent(lookup); + + // Skin has configuration. + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) + return d; + + return new DefaultSkinComponentsContainer(container => { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents when containerLookup.Ruleset != null: - Debug.Assert(containerLookup.Ruleset.ShortName == ManiaRuleset.SHORT_NAME); + var combo = container.ChildrenOfType().FirstOrDefault(); - var rulesetHUDComponents = Skin.GetDrawableComponent(lookup); - - rulesetHUDComponents ??= new DefaultSkinComponentsContainer(container => - { - var combo = container.ChildrenOfType().FirstOrDefault(); - - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; - } - }) - { - new LegacyManiaComboCounter(), - }; - - return rulesetHUDComponents; - } - - break; + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 9a8eaa7d7d..c2381fff88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -45,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) + return base.GetDrawableComponent(lookup); + // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -53,34 +56,36 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - // Our own ruleset components default. - switch (containerLookup.Target) + return new DefaultSkinComponentsContainer(container => { - case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: - return new DefaultSkinComponentsContainer(container => - { - var keyCounter = container.OfType().FirstOrDefault(); + var keyCounter = container.OfType().FirstOrDefault(); - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyComboCounter(), - new LegacyKeyCounterDisplay(), - } - }; - } + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } - return null; + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + Children = new Drawable[] + { + new LegacyDefaultComboCounter(), + new LegacyKeyCounterDisplay(), + } + }; case OsuSkinComponentLookup osuComponent: switch (osuComponent.Component) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index f44daa1ecb..7466442674 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -443,7 +443,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType().Any()); AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType().Count() == 1); - AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter + AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyDefaultComboCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 2da09839e9..8f6e634dd6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -13,7 +13,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; @@ -24,6 +23,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -367,10 +367,19 @@ namespace osu.Game.Skinning case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: if (containerLookup.Ruleset != null) { - return new Container + return new DefaultSkinComponentsContainer(container => { - RelativeSizeAxes = Axes.Both, - Child = new LegacyComboCounter(), + var combo = container.OfType().FirstOrDefault(); + + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + new LegacyDefaultComboCounter() }; } From 7666e8b9320b8e84a4ff621cc53bf477518b7b1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:11:57 +0900 Subject: [PATCH 566/670] Remove `SupportsClosestAnchor` for the time being This may have had a good reason to be added, but I can't find that reason, so let's keep things simple for the time being. --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 5 +---- .../Skinning/Legacy/LegacyManiaComboCounter.cs | 4 +--- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 --- osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs | 3 +-- osu.Game/Skinning/ISerialisableDrawable.cs | 8 -------- 5 files changed, 3 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index ad515528fb..9a4eea993d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -10,20 +10,17 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; -using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonManiaComboCounter : ComboCounter, ISerialisableDrawable + public partial class ArgonManiaComboCounter : ComboCounter { private OsuSpriteText text = null!; protected override double RollingDuration => 500; protected override Easing RollingEasing => Easing.OutQuint; - bool ISerialisableDrawable.SupportsClosestAnchor => false; - [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index a51a50c604..c1fe4a1028 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -11,10 +11,8 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Legacy { - public partial class LegacyManiaComboCounter : LegacyComboCounter, ISerialisableDrawable + public partial class LegacyManiaComboCounter : LegacyComboCounter { - bool ISerialisableDrawable.SupportsClosestAnchor => false; - [BackgroundDependencyLoader] private void load(ISkinSource skin) { diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 7bbd875542..484af34603 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -444,9 +444,6 @@ namespace osu.Game.Overlays.SkinEditor drawableComponent.Origin = Anchor.TopCentre; drawableComponent.Anchor = Anchor.TopCentre; drawableComponent.Y = targetContainer.DrawSize.Y / 2; - - if (!component.SupportsClosestAnchor) - component.UsesFixedAnchor = true; } try diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index c86221c7fb..722ffd6d07 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -103,8 +103,7 @@ namespace osu.Game.Overlays.SkinEditor { var closestItem = new TernaryStateRadioMenuItem("Closest", MenuItemType.Standard, _ => applyClosestAnchors()) { - State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor), }, - Action = { Disabled = selection.Any(c => !c.Item.SupportsClosestAnchor) }, + State = { Value = GetStateFromSelection(selection, c => !c.Item.UsesFixedAnchor) } }; yield return new OsuMenuItem("Anchor") diff --git a/osu.Game/Skinning/ISerialisableDrawable.cs b/osu.Game/Skinning/ISerialisableDrawable.cs index 898186bcc1..c9dcaca6d1 100644 --- a/osu.Game/Skinning/ISerialisableDrawable.cs +++ b/osu.Game/Skinning/ISerialisableDrawable.cs @@ -27,14 +27,6 @@ namespace osu.Game.Skinning /// bool IsEditable => true; - /// - /// Whether this component supports the "closest" anchor. - /// - /// - /// This is disabled by some components that shift position automatically. - /// - bool SupportsClosestAnchor => true; - /// /// In the context of the skin layout editor, whether this has a permanent anchor defined. /// If , this 's is automatically determined by proximity, From 3f20f05801d8a23d970b2954cd5b956e929284c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:39:01 +0900 Subject: [PATCH 567/670] Remove unnecessary `UsesFixedAnchor` specifications --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 2 -- .../Skinning/Legacy/LegacyManiaComboCounter.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 9a4eea993d..16f2109896 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon text.FadeOut(200, Easing.InQuint); } }); - - UsesFixedAnchor = true; } [Resolved] diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index c1fe4a1028..5832210836 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -22,8 +22,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy PopOutCountText.Anchor = Anchor.Centre; PopOutCountText.Origin = Anchor.Centre; PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; - - UsesFixedAnchor = true; } [Resolved] From 161734af954994ba2095cb77ca19498ba9fdf08f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 15:46:57 +0900 Subject: [PATCH 568/670] Simplify argon mania combo counter implementation by sharing with base counter --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 36 +------------------ .../Argon/ManiaArgonSkinTransformer.cs | 1 + .../Screens/Play/HUD/ArgonComboCounter.cs | 14 ++++---- 3 files changed, 9 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 16f2109896..e77650bed1 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -4,41 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonManiaComboCounter : ComboCounter + public partial class ArgonManiaComboCounter : ArgonComboCounter { - private OsuSpriteText text = null!; - - protected override double RollingDuration => 500; - protected override Easing RollingEasing => Easing.OutQuint; - - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) - { - Current.BindTo(scoreProcessor.Combo); - Current.BindValueChanged(combo => - { - if (combo.OldValue == 0 && combo.NewValue > 0) - text.FadeIn(200, Easing.OutQuint); - else if (combo.OldValue > 0 && combo.NewValue == 0) - { - if (combo.OldValue > 1) - text.FlashColour(Color4.Red, 2000, Easing.OutQuint); - - text.FadeOut(200, Easing.InQuint); - } - }); - } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } = null!; @@ -47,7 +19,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon protected override void LoadComplete() { base.LoadComplete(); - text.Alpha = Current.Value > 0 ? 1 : 0; direction = scrollingInfo.Direction.GetBoundCopy(); direction.BindValueChanged(_ => updateAnchor()); @@ -67,10 +38,5 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) Y = -Y; } - - protected override IHasText CreateText() => text = new OsuSpriteText - { - Font = OsuFont.Torus.With(size: 32, fixedWidth: true), - }; } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index f541cea0f5..224db77f59 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -46,6 +46,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon if (combo != null) { + combo.ShowLabel.Value = false; combo.Anchor = Anchor.TopCentre; combo.Origin = Anchor.Centre; combo.Y = 200; diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index db0480c566..3f74a8d4e8 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD { public partial class ArgonComboCounter : ComboCounter { - private ArgonCounterTextComponent text = null!; + protected ArgonCounterTextComponent Text = null!; protected override double RollingDuration => 250; @@ -43,16 +43,16 @@ namespace osu.Game.Screens.Play.HUD bool wasIncrease = combo.NewValue > combo.OldValue; bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0; - float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); + float newScale = Math.Clamp(Text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f); float duration = wasMiss ? 2000 : 500; - text.NumberContainer + Text.NumberContainer .ScaleTo(new Vector2(newScale)) .ScaleTo(Vector2.One, duration, Easing.OutQuint); if (wasMiss) - text.FlashColour(Color4.Red, duration, Easing.OutQuint); + Text.FlashColour(Color4.Red, duration, Easing.OutQuint); }); } @@ -70,8 +70,8 @@ namespace osu.Game.Screens.Play.HUD { int digitsRequiredForDisplayCount = getDigitsRequiredForDisplayCount(); - if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length) - text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); + if (digitsRequiredForDisplayCount != Text.WireframeTemplate.Length) + Text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount); } private int getDigitsRequiredForDisplayCount() @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(int count) => $@"{count}x"; - protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) + protected override IHasText CreateText() => Text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { WireframeOpacity = { BindTarget = WireframeOpacity }, ShowLabel = { BindTarget = ShowLabel }, From 2114f092c7422d11d16020c26865f29fd3f2d4c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 16:31:47 +0900 Subject: [PATCH 569/670] Add failing test coverage showing coordinate truncation --- .../Formats/LegacyBeatmapDecoderTest.cs | 34 +++++++++++++++++++ .../Resources/hitobject-coordinates-lazer.osu | 6 ++++ .../hitobject-coordinates-legacy.osu | 5 +++ 3 files changed, 45 insertions(+) create mode 100644 osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu create mode 100644 osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 19378821b3..54ebebeb7b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -468,6 +468,40 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestDecodeBeatmapHitObjectCoordinatesLegacy() + { + var decoder = new LegacyBeatmapDecoder(); + + using (var resStream = TestResources.OpenResource("hitobject-coordinates-legacy.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(256, 256), positionData!.Position); + } + } + + [Test] + public void TestDecodeBeatmapHitObjectCoordinatesLazer() + { + var decoder = new LegacyBeatmapDecoder(LegacyBeatmapEncoder.FIRST_LAZER_VERSION); + + using (var resStream = TestResources.OpenResource("hitobject-coordinates-lazer.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var hitObjects = decoder.Decode(stream).HitObjects; + + var positionData = hitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position); + } + } + [Test] public void TestDecodeBeatmapHitObjects() { diff --git a/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu b/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu new file mode 100644 index 0000000000..bb898a1521 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu @@ -0,0 +1,6 @@ +osu file format v128 + +[HitObjects] +// Coordinates should be preserves in lazer beatmaps. + +256.99853,256.001,1000,49,0,0:0:0:0: diff --git a/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu b/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu new file mode 100644 index 0000000000..e914c2fb36 --- /dev/null +++ b/osu.Game.Tests/Resources/hitobject-coordinates-legacy.osu @@ -0,0 +1,5 @@ +osu file format v14 + +[HitObjects] +// Coordinates should be truncated to int values in legacy beatmaps. +256.99853,256.001,1000,49,0,0:0:0:0: From d072c6a743ffb6a2a3614587703de90588dbe169 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 16:19:25 +0900 Subject: [PATCH 570/670] Fix hit object coordinates being truncated to `int` values Closes https://github.com/ppy/osu/issues/29340. --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 6 ++++++ osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 30a78a16ed..ca4fadf458 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -18,6 +18,12 @@ namespace osu.Game.Beatmaps.Formats { public const int LATEST_VERSION = 14; + /// + /// The .osu format (beatmap) version. + /// + /// osu!stable's versions end at . + /// osu!lazer's versions starts at . + /// protected readonly int FormatVersion; protected LegacyDecoder(int version) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 37a87462ca..8e6ffa20cc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects.Legacy protected readonly double Offset; /// - /// The beatmap version. + /// The .osu format (beatmap) version. /// protected readonly int FormatVersion; @@ -48,7 +48,10 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] split = text.Split(','); - Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); + Vector2 pos = + FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION + ? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)) + : new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); double startTime = Parsing.ParseDouble(split[2]) + Offset; From 52b2d73e046dd4958658b75212d161dbbe176077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:10:18 +0900 Subject: [PATCH 571/670] Only show daily challenge notification if it started within the last 30 minutes --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index a5616b95a0..b1f276b2ec 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -104,8 +104,7 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true)); - dailyChallengeChanged(postNotification: false); + info.BindValueChanged(dailyChallengeChanged, true); } protected override void Update() @@ -131,28 +130,29 @@ namespace osu.Game.Screens.Menu } } - private void dailyChallengeChanged(bool postNotification) + private void dailyChallengeChanged(ValueChangedEvent info) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (info.Value == null) + if (this.info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); + var roomRequest = new GetRoomRequest(this.info.Value.Value.RoomID); roomRequest.Success += room => { Room = room; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; - if (postNotification) + // We only want to notify the user if a new challenge recently went live. + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600) notificationOverlay?.Post(new NewDailyChallengeNotification(room)); updateCountdown(); From e146c8e23083307c5ea2f222243b16bf612bbcc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:11:11 +0900 Subject: [PATCH 572/670] Ensure only one daily challenge notification is fired per room --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index b1f276b2ec..ac04afdc4d 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -130,6 +130,8 @@ namespace osu.Game.Screens.Menu } } + private long? lastNotifiedDailyChallengeRoomId; + private void dailyChallengeChanged(ValueChangedEvent info) { UpdateState(); @@ -152,8 +154,11 @@ namespace osu.Game.Screens.Menu cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; // We only want to notify the user if a new challenge recently went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600) + if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + { + lastNotifiedDailyChallengeRoomId = room.RoomID.Value; notificationOverlay?.Post(new NewDailyChallengeNotification(room)); + } updateCountdown(); Scheduler.AddDelayed(updateCountdown, 1000, true); From f6ada68e47b8f686b8d7a78a43652c67cda48ca0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:26:54 +0900 Subject: [PATCH 573/670] Fix migration failure due to change in class name --- osu.Game/Skinning/Skin.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index a885a0083d..3a83815f0e 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -251,13 +251,15 @@ namespace osu.Game.Skinning { case 1: { + // Combo counters were moved out of the global HUD components into per-ruleset. + // This is to allow some rulesets to customise further (ie. mania and catch moving the combo to within their play area). if (target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents || !layout.TryGetDrawableInfo(null, out var globalHUDComponents) || resources == null) break; var comboCounters = globalHUDComponents.Where(c => - c.Type.Name == nameof(LegacyComboCounter) || + c.Type.Name == nameof(LegacyDefaultComboCounter) || c.Type.Name == nameof(DefaultComboCounter) || c.Type.Name == nameof(ArgonComboCounter)).ToArray(); From 0a8e342830059a0c71a5b0515038d70146319e28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:37:39 +0900 Subject: [PATCH 574/670] Fix occasionally `ChatOverlay` test failures due to RNG usage See https://github.com/ppy/osu/actions/runs/10302758137/job/28517150950. Same ID gets chosen twice for PM channel. --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 58feab4ebb..7f9b9acf1c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -717,9 +717,12 @@ namespace osu.Game.Tests.Visual.Online Type = ChannelType.Public, }; + private static int privateChannelUser = DummyAPIAccess.DUMMY_USER_ID + 1; + private Channel createPrivateChannel() { - int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1); + int id = Interlocked.Increment(ref privateChannelUser); + return new Channel(new APIUser { Id = id, From 18c80870d81e004a0b8979015bcb15a135db00a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:40:48 +0900 Subject: [PATCH 575/670] Update one more RNG usage in same tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 7f9b9acf1c..372cf60853 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -19,7 +19,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Logging; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -122,7 +121,7 @@ namespace osu.Game.Tests.Visual.Online return true; case PostMessageRequest postMessage: - postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000)) + postMessage.TriggerSuccess(new Message(getNextTestID()) { Content = postMessage.Message.Content, ChannelId = postMessage.Message.ChannelId, @@ -717,11 +716,9 @@ namespace osu.Game.Tests.Visual.Online Type = ChannelType.Public, }; - private static int privateChannelUser = DummyAPIAccess.DUMMY_USER_ID + 1; - private Channel createPrivateChannel() { - int id = Interlocked.Increment(ref privateChannelUser); + int id = getNextTestID(); return new Channel(new APIUser { @@ -742,6 +739,10 @@ namespace osu.Game.Tests.Visual.Online }; } + private static int testId = DummyAPIAccess.DUMMY_USER_ID + 1; + + private static int getNextTestID() => Interlocked.Increment(ref testId); + private partial class TestChatOverlay : ChatOverlay { public bool SlowLoading { get; set; } From c8a77271994778a53eed43a37cbe07a324dc07f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 17:46:10 +0900 Subject: [PATCH 576/670] Make ID retrieval global to all tests and fix multiple other usages --- osu.Game.Tests/Resources/TestResources.cs | 9 +++++++-- .../Visual/Multiplayer/TestSceneMultiplayer.cs | 4 ++-- osu.Game.Tests/Visual/Online/TestSceneChannelList.cs | 8 ++++---- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 9 +++------ 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index a77dc8d49b..e0572e604c 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -73,7 +73,12 @@ namespace osu.Game.Tests.Resources private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz"); - private static int importId; + private static int testId = 1; + + /// + /// Get a unique int value which is incremented each call. + /// + public static int GetNextTestID() => Interlocked.Increment(ref testId); /// /// Create a test beatmap set model. @@ -88,7 +93,7 @@ namespace osu.Game.Tests.Resources RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length]; - int setId = Interlocked.Increment(ref importId); + int setId = GetNextTestID(); var metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 3306b6624e..ad7e211354 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -139,8 +139,8 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addRandomPlayer() { - int randomUser = RNG.Next(200000, 500000); - multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" }); + int id = TestResources.GetNextTestID(); + multiplayerClient.AddUser(new APIUser { Id = id, Username = $"user {id}" }); } private void removeLastUser() diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index a0cca5f53d..5f77e084da 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -9,13 +9,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.ChannelList; using osu.Game.Overlays.Chat.Listing; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online { @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createRandomPublicChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel { Name = $"#channel-{id}", @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createRandomPrivateChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel(new APIUser { Id = id, @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createRandomAnnounceChannel() { - int id = RNG.Next(0, 10000); + int id = TestResources.GetNextTestID(); return new Channel { Name = $"Announce {id}", diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 372cf60853..a47205094e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -32,6 +32,7 @@ using osu.Game.Overlays.Chat.ChannelList; using osuTK; using osuTK.Input; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Online { @@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Online return true; case PostMessageRequest postMessage: - postMessage.TriggerSuccess(new Message(getNextTestID()) + postMessage.TriggerSuccess(new Message(TestResources.GetNextTestID()) { Content = postMessage.Message.Content, ChannelId = postMessage.Message.ChannelId, @@ -718,7 +719,7 @@ namespace osu.Game.Tests.Visual.Online private Channel createPrivateChannel() { - int id = getNextTestID(); + int id = TestResources.GetNextTestID(); return new Channel(new APIUser { @@ -739,10 +740,6 @@ namespace osu.Game.Tests.Visual.Online }; } - private static int testId = DummyAPIAccess.DUMMY_USER_ID + 1; - - private static int getNextTestID() => Interlocked.Increment(ref testId); - private partial class TestChatOverlay : ChatOverlay { public bool SlowLoading { get; set; } From 8fdd94090b6fde9550ae8c1bac4e51044dc971da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 18:02:37 +0900 Subject: [PATCH 577/670] Show object inspector values during placement --- .../Edit/OsuHitObjectInspector.cs | 10 +++---- .../Compose/Components/HitObjectInspector.cs | 27 +++++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs index 27e7d5497c..b31fe05995 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectInspector.cs @@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuHitObjectInspector : HitObjectInspector { - protected override void AddInspectorValues() + protected override void AddInspectorValues(HitObject[] objects) { - base.AddInspectorValues(); + base.AddInspectorValues(objects); - if (EditorBeatmap.SelectedHitObjects.Count > 0) + if (objects.Length > 0) { - var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!; - var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!; + var firstInSelection = (OsuHitObject)objects.MinBy(ho => ho.StartTime)!; + var lastInSelection = (OsuHitObject)objects.MaxBy(ho => ho.GetEndTime())!; Debug.Assert(firstInSelection != null && lastInSelection != null); diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs index de23147e7b..b74a89e3fe 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Threading; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components base.LoadComplete(); EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText(); + EditorBeatmap.PlacementObject.BindValueChanged(_ => updateInspectorText()); EditorBeatmap.TransactionBegan += updateInspectorText; EditorBeatmap.TransactionEnded += updateInspectorText; updateInspectorText(); @@ -29,24 +31,33 @@ namespace osu.Game.Screens.Edit.Compose.Components rollingTextUpdate?.Cancel(); rollingTextUpdate = null; - AddInspectorValues(); + HitObject[] objects; + + if (EditorBeatmap.SelectedHitObjects.Count > 0) + objects = EditorBeatmap.SelectedHitObjects.ToArray(); + else if (EditorBeatmap.PlacementObject.Value != null) + objects = new[] { EditorBeatmap.PlacementObject.Value }; + else + objects = Array.Empty(); + + AddInspectorValues(objects); // I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes. // This is a good middle-ground for the time being. - if (EditorBeatmap.SelectedHitObjects.Count > 0) + if (objects.Length > 0) rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250); } - protected virtual void AddInspectorValues() + protected virtual void AddInspectorValues(HitObject[] objects) { - switch (EditorBeatmap.SelectedHitObjects.Count) + switch (objects.Length) { case 0: AddValue("No selection"); break; case 1: - var selected = EditorBeatmap.SelectedHitObjects.Single(); + var selected = objects.Single(); AddHeader("Type"); AddValue($"{selected.GetType().ReadableName()}"); @@ -105,13 +116,13 @@ namespace osu.Game.Screens.Edit.Compose.Components default: AddHeader("Selected Objects"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}"); + AddValue($"{objects.Length:#,0.##}"); AddHeader("Start Time"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms"); + AddValue($"{objects.Min(o => o.StartTime):#,0.##}ms"); AddHeader("End Time"); - AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms"); + AddValue($"{objects.Max(o => o.GetEndTime()):#,0.##}ms"); break; } } From 3e634a14a4794e8b18f5920c807180896e31603c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 18:43:37 +0900 Subject: [PATCH 578/670] Add temporary debug code for multiplayer test failures --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 77ede1fd35..ee2f1d64dc 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -782,7 +782,21 @@ namespace osu.Game.Online.Multiplayer } catch (Exception ex) { - throw new AggregateException($"Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); + // Temporary code to attempt to figure out long-term failing tests. + bool success = true; + int indexOf = -1234; + + try + { + indexOf = Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID)); + Room.Playlist[indexOf] = item; + } + catch + { + success = false; + } + + throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); } ItemChanged?.Invoke(item); From fa9a835eb5366ea55def9aa280700c55b51ef28f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 19:29:23 +0900 Subject: [PATCH 579/670] Make icon smaller --- osu.Game/Overlays/Volume/MuteButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index a04d79bd20..bee5cce199 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Volume Current.BindValueChanged(muted => { icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; - icon.Size = new Vector2(muted.NewValue ? 18 : 20); + icon.Size = new Vector2(muted.NewValue ? 12 : 16); icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 }; }, true); } From 179a3ad8dd31c8ecfb4684a2ad5529b91a87d1ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Aug 2024 19:55:56 +0900 Subject: [PATCH 580/670] Hack around the border looking ugly This is an o!f issue because borders are applied into the individual sprites of the container via masking, rather than being isolated to the container itself. In this case, it'll be applied to the "flash" sprite, which is using additive blending, causing further issues. --- osu.Game/Overlays/Volume/MuteButton.cs | 32 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index bee5cce199..878842867d 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -7,13 +7,13 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Volume { @@ -33,29 +33,28 @@ namespace osu.Game.Overlays.Volume } } - private Color4 hoveredColour, unhoveredColour; + private ColourInfo hoveredBorderColour; + private ColourInfo unhoveredBorderColour; + private CompositeDrawable border = null!; public MuteButton() { const float width = 30; const float height = 30; - Content.BorderThickness = 3; + Size = new Vector2(width, height); Content.CornerRadius = height / 2; Content.CornerExponent = 2; - Size = new Vector2(width, height); - Action = () => Current.Value = !Current.Value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredColour = colours.PinkLight; - - Content.BorderColour = unhoveredColour = colours.Gray1; BackgroundColour = colours.Gray1; + hoveredBorderColour = colours.PinkLight; + unhoveredBorderColour = colours.Gray1; SpriteIcon icon; @@ -65,6 +64,19 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, + }, + border = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 3, + BorderColour = unhoveredBorderColour, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } } }); @@ -78,13 +90,13 @@ namespace osu.Game.Overlays.Volume protected override bool OnHover(HoverEvent e) { - Content.TransformTo, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint); + border.TransformTo(nameof(BorderColour), hoveredBorderColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - Content.TransformTo, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint); + border.TransformTo(nameof(BorderColour), unhoveredBorderColour, 500, Easing.OutQuint); } protected override bool OnMouseDown(MouseDownEvent e) From 3896a081a56529044848830bd913bdc3313d16df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Aug 2024 22:50:56 +0900 Subject: [PATCH 581/670] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3b3385ecfe..ae30563a19 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 196d5594ad..9fa1b691e9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From c6fa348d82f48f07d516885976288c4aa437f3bc Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 9 Aug 2024 23:03:03 +0900 Subject: [PATCH 582/670] Add sound design for daily challenge intro animation --- osu.Game/Screens/Menu/ButtonSystem.cs | 2 +- .../DailyChallenge/DailyChallengeIntro.cs | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index e9fff9bb07..0997ab8003 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Menu }); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M)); buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L)); - buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); + buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index d00a1ef1e9..83ea0f8088 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -4,6 +4,8 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -68,6 +70,18 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge [Resolved] private MusicController musicController { get; set; } = null!; + private Sample? dateWindupSample; + private Sample? dateImpactSample; + private Sample? beatmapWindupSample; + private Sample? beatmapImpactSample; + + private SampleChannel? dateWindupChannel; + private SampleChannel? dateImpactChannel; + private SampleChannel? beatmapWindupChannel; + private SampleChannel? beatmapImpactChannel; + + private IDisposable? duckOperation; + public DailyChallengeIntro(Room room) { this.room = room; @@ -79,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider); [BackgroundDependencyLoader] - private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config) + private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio) { const float horizontal_info_size = 500f; @@ -323,6 +337,11 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID })) beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get(OsuSetting.PreferNoVideo)); } + + dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup"); + dateImpactSample = audio.Samples.Get(@"DailyChallenge/date-impact"); + beatmapWindupSample = audio.Samples.Get(@"DailyChallenge/beatmap-windup"); + beatmapImpactSample = audio.Samples.Get(@"DailyChallenge/beatmap-impact"); } public override void OnEntering(ScreenTransitionEvent e) @@ -338,6 +357,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge this.FadeInFromZero(400, Easing.OutQuint); updateAnimationState(); + + playDateWindupSample(); } public override void OnSuspending(ScreenTransitionEvent e) @@ -346,6 +367,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge base.OnSuspending(e); } + protected override void Dispose(bool isDisposing) + { + resetAudio(); + base.Dispose(isDisposing); + } + private void updateAnimationState() { if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) @@ -380,6 +407,29 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge .Then() .MoveToY(0, 4000); + using (BeginDelayedSequence(150)) + { + Schedule(() => + { + playDateImpactSample(); + playBeatmapWindupSample(); + + duckOperation?.Dispose(); + duckOperation = musicController.Duck(new DuckParameters + { + RestoreDuration = 1500f, + }); + }); + + using (BeginDelayedSequence(2750)) + { + Schedule(() => + { + duckOperation?.Dispose(); + }); + } + } + using (BeginDelayedSequence(1000)) { beatmapContent @@ -406,6 +456,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge shouldBePlayingMusic = true; DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item); ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item); + playBeatmapImpactSample(); }); } @@ -425,6 +476,39 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge } } + private void playDateWindupSample() + { + dateWindupChannel = dateWindupSample?.GetChannel(); + dateWindupChannel?.Play(); + } + + private void playDateImpactSample() + { + dateImpactChannel = dateImpactSample?.GetChannel(); + dateImpactChannel?.Play(); + } + + private void playBeatmapWindupSample() + { + beatmapWindupChannel = beatmapWindupSample?.GetChannel(); + beatmapWindupChannel?.Play(); + } + + private void playBeatmapImpactSample() + { + beatmapImpactChannel = beatmapImpactSample?.GetChannel(); + beatmapImpactChannel?.Play(); + } + + private void resetAudio() + { + dateWindupChannel?.Stop(); + dateImpactChannel?.Stop(); + beatmapWindupChannel?.Stop(); + beatmapImpactChannel?.Stop(); + duckOperation?.Dispose(); + } + private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen { private readonly OverlayColourProvider colourProvider; From 81777f22b4463bafa8de3da717db42193b7f904a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Aug 2024 00:11:39 +0900 Subject: [PATCH 583/670] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ae30563a19..b5a355a77f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fa1b691e9..7b3903c352 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From c29b40ae6593e21d3fdebcb4a8dbff37f1edbbca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 10 Aug 2024 02:08:02 +0900 Subject: [PATCH 584/670] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c4d76a2441..6b07a33af2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 2dee8bef7e861b435bcaf8c737c8255b5aafe0a4 Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Fri, 9 Aug 2024 22:50:37 +0200 Subject: [PATCH 585/670] Add option to hide song progress time/text --- osu.Game/Localisation/HUD/SongProgressStrings.cs | 10 ++++++++++ osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 4 ++++ osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/osu.Game/Localisation/HUD/SongProgressStrings.cs b/osu.Game/Localisation/HUD/SongProgressStrings.cs index 4c621e8e8c..332f15cb17 100644 --- a/osu.Game/Localisation/HUD/SongProgressStrings.cs +++ b/osu.Game/Localisation/HUD/SongProgressStrings.cs @@ -19,6 +19,16 @@ namespace osu.Game.Localisation.HUD /// public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown"); + /// + /// "Show time" + /// + public static LocalisableString ShowTime => new TranslatableString(getKey(@"show_time"), "Show time"); + + /// + /// "Whether the passed and remaining time should be shown" + /// + public static LocalisableString ShowTimeDescription => new TranslatableString(getKey(@"show_time_description"), "Whether the passed and remaining time should be shown"); + private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 7db3f9fd3c..ebebfebfb3 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -26,6 +26,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] + public Bindable ShowTime { get; } = new BindableBool(true); + [Resolved] private Player? player { get; set; } @@ -90,6 +93,7 @@ namespace osu.Game.Screens.Play.HUD Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); + ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); } protected override void UpdateObjects(IEnumerable objects) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index f01c11855c..1586ac72a1 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -33,6 +33,9 @@ namespace osu.Game.Screens.Play.HUD [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] + public Bindable ShowTime { get; } = new BindableBool(true); + [Resolved] private Player? player { get; set; } @@ -82,9 +85,11 @@ namespace osu.Game.Screens.Play.HUD { Interactive.BindValueChanged(_ => updateBarVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); + ShowTime.BindValueChanged(_ => updateTimeVisibility(), true); base.LoadComplete(); } + protected override void UpdateObjects(IEnumerable objects) { @@ -129,6 +134,14 @@ namespace osu.Game.Screens.Play.HUD updateInfoMargin(); } + private void updateTimeVisibility() + { + info.FadeTo(ShowTime.Value ? 1 : 0, transition_duration, Easing.In); + + updateInfoMargin(); + } + + private void updateInfoMargin() { float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); From d01e76d9db3c4cffff803114edcbdf4c32dd6b5a Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Fri, 9 Aug 2024 23:08:22 +0200 Subject: [PATCH 586/670] Fix double blank line --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 1586ac72a1..adb93c3ba3 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -89,7 +89,6 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); } - protected override void UpdateObjects(IEnumerable objects) { From fed5b9d7477f2118609b742c139c72aac800fd11 Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Sat, 10 Aug 2024 09:45:30 +0200 Subject: [PATCH 587/670] Fix one more inspectcode warning --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index adb93c3ba3..6b2bb2b718 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -140,7 +140,6 @@ namespace osu.Game.Screens.Play.HUD updateInfoMargin(); } - private void updateInfoMargin() { float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); From 2233602184b725d4a468b53737f02d7788639c55 Mon Sep 17 00:00:00 2001 From: clayton Date: Sun, 11 Aug 2024 09:45:42 -0700 Subject: [PATCH 588/670] Update mania replay decode test to include 18K keypress --- .../Formats/LegacyScoreDecoderTest.cs | 9 ++++----- .../Resources/Replays/mania-replay.osr | Bin 1012 -> 1042 bytes 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 3759bfd034..070ade4ad9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -19,7 +19,7 @@ using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -65,14 +65,13 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore); Assert.AreEqual(3, score.ScoreInfo.MaxCombo); - Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic)); - Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL")); - Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL")); + Assert.That(score.ScoreInfo.APIMods.Select(m => m.Acronym), Is.EquivalentTo(new[] { "CL", "9K", "DS" })); Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001)); Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); - Assert.That(score.Replay.Frames, Is.Not.Empty); + Assert.That(score.Replay.Frames, Has.One.Matches(frame => + frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key16 }))); } } diff --git a/osu.Game.Tests/Resources/Replays/mania-replay.osr b/osu.Game.Tests/Resources/Replays/mania-replay.osr index da1a7bdd28e5b89eddd9742bce2b27bc8e75e6f5..ad55a5a31873acc943379b6391bb068ef92397b7 100644 GIT binary patch delta 941 zcmV;e15*6-2a*VoEL&k=VPRomVPRomVPRomVPRomVPRomVPRomVPRomVFCaE00000 z009610PCp?00RI3000033lK3dIX5zVF)T4SFgZAUF)U$Fy<3(_*a(UP003P80Du4` zM3IL|f2kp_Cwm-NDGQfLnv}R{A+5uM=V$!>%NF%V;(li`g9UYd{}pt#j-NjTL^Rx$ z)8vox9t#(tVgh24S9qwBpR*G=!suY`TLgA|2YbKlAt?C3gt0(^Ne~qSalq8ciz#p@2y$@@79OY<$k=i&@ z$%){vIws|+*G$%o*Abhy7QAvG3_AklP}%Uj6WtwNj^#D~Ap6s`y6)+@Vs|9!>rhF$ z(Th@AK+#MQ+@eBuu>#W^VSow$rY2vZvtyLf$Fe-%Y}I8;wFHWVyQB;i47XyCq#ga- zf6<@ARV=7N+nveRe(H&`|49MZB_STJbhkQ&Kg%tRZ5su!GX3k;q-nc^O3L)?dAyz6 zkq*PQof<5?`eqvXkU4Gq?CG7&!{XQ>l-81EDy%S!hcCnQx-Eg^znRa2m3+kl(&{_4 z|E#XY&k`FWw(AwgOBN}Z{pzUR1N`=ce>&ZHKn37E`?&xm+$MMK8G%yB-KT2|g&Q|v z!KGRky;bp!dh$;i3BgA{n8d#`oL{ZD{RXnE>twCR4liAX%w50Txmgdjn0*~7b8UJS z9`hlcqw752ctoxbZSXM@6|MMd(ofzlz~{ZqD%2&ADT$6q)$)FS?(9xRT2XjCf1zRl z=;!6R(l-}a+w+m{IJ5YVzyr}Z1|w6+wA>Y>tB-E9T=9J7eOSVn*3{NocS0mZaN9Iz z*8||a6~)(jJ?Q4h=*1y|Up7!MiC7a#ZYI3i;~I4y+@wb|cGLJW*H)1H2@k~Rna5gx zC}}6l4~U_i7R;**PnLpaABm`ie?{Ulv?yRhX^Ccj3l6au<|2+bfq5wf z{;YNi$U9q;a9n;o1AC&o44(&hq*5gqE1yu^<%DjP#j?h@8p1e)RU%gINB4K-`759> z6$9)02}Os4pSJ8KLxV_3sf!8h1%oh54Pr5{yWq7hhBpIMpL1g4;G#Hr7_gz8oO)mX P-u?|V0000000000tK-15 delta 911 zcmV;A191G32=oV#EL$>UWo0=sIWaXiGGjF`V>B^gIWRagV=^){GcsglGy(ts00000 z009610PCp?00RI3000003lK3dIX5zVF)T4SFgZAUF)U$Fy<3(_*a&O`003P803ZP5 zLy?C{f3PYVk96LhNin}dI^y)M#Q-6F;~hK6;6v0f4($qcC=D#w1v}gT>+Q44apIe> z^u{Hu`T^&O5+{?7)C=5^o4~{|a(=klR#xS2K1jz$5#VC2cu?g{{PKLP6wJ^#SieUk zU{__vBIA6}WQCuDj>EFa^^_#$Q4P>ae;2H4=ZGmx7w}kC2MuRN0?rGS(8}Y5 zhz;zQ^pgdCFAA95G5naWn3}GRN*enPdqN;!X`#^cNS3(>dQ8HnIdQ)YOF?B;rDi!z~! ze>k`!wUtQ?QkF@%v-^b`=F}eWcIcyuEA?+EC5nJ6&VHDlo)zLnjA}iYry^Wc282uw zb8MhH5)cj#aw!_hD@mb6mKMjw{o@f8;PD*nHwXocs~eYj)Vr=h%$l3Zu|vmp^tLml zymcsXvkSx^NtzJrIgdT3GIkW!%>zeQe|zJn8w6pYSsUr1=*o_Q#S)6A-dHA@d9~(^0+ax|D@!;n;Z|| zFvVlqvtKZJf2x8EVoS#`3YPzS-*vaun`i1N*BG`MvK_+XgTPh^GgHpdy!e==e?Np^ zP79t-TF}QHA=p$CMZ8h|*&agcSs?d99UKOtBUW5%$#uK?C(WYoiHQt9+}D}2*H`GI z+MHqHE17uF?Dqy%$~lzbk4H;u{VzwEFmdj}XdWCiMA_1-@_?MecxP^r?UDsYE^qoo z$|(tSLa2UCM@zI7zrxT> Date: Sun, 11 Aug 2024 09:45:43 -0700 Subject: [PATCH 589/670] Fix mouseX legacy replay parsing for high key counts in mania --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index ba1fdd6adf..6ad118547b 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -280,8 +280,11 @@ namespace osu.Game.Scoring.Legacy continue; } + // In mania, mouseX encodes the pressed keys in the lower 20 bits + int mouseXParseLimit = currentRuleset.RulesetInfo.OnlineID == 3 ? (1 << 20) - 1 : Parsing.MAX_COORDINATE_VALUE; + float diff = Parsing.ParseFloat(split[0]); - float mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE); + float mouseX = Parsing.ParseFloat(split[1], mouseXParseLimit); float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE); lastTime += diff; From d2eb6ccb8caa7455d1daba4ca9d2518ecbec4ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2024 14:27:21 +0900 Subject: [PATCH 590/670] Standardise skin transformer code structure --- .../Legacy/CatchLegacySkinTransformer.cs | 49 +++++++------- .../Legacy/OsuLegacySkinTransformer.cs | 65 ++++++++++--------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index df8c04638d..4efdafc034 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -31,10 +31,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - - // Modifications for global components. + // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -43,27 +40,33 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy return d; // Our own ruleset components default. - // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. - return new DefaultSkinComponentsContainer(container => + switch (containerLookup.Target) { - var keyCounter = container.OfType().FirstOrDefault(); + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } - }) - { - Children = new Drawable[] - { - new LegacyKeyCounterDisplay(), - } - }; + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } + }) + { + Children = new Drawable[] + { + new LegacyKeyCounterDisplay(), + } + }; + } + + return null; case CatchSkinComponentLookup catchSkinComponent: switch (catchSkinComponent.Component) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index c2381fff88..aad8ffb1cf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -45,9 +45,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -56,42 +53,50 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - return new DefaultSkinComponentsContainer(container => + // Our own ruleset components default. + switch (containerLookup.Target) { - var keyCounter = container.OfType().FirstOrDefault(); + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var keyCounter = container.OfType().FirstOrDefault(); - if (keyCounter != null) - { - // set the anchor to top right so that it won't squash to the return button to the top - keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; - } + if (keyCounter != null) + { + // set the anchor to top right so that it won't squash to the return button to the top + keyCounter.Anchor = Anchor.CentreRight; + keyCounter.Origin = Anchor.CentreRight; + keyCounter.X = 0; + // 340px is the default height inherit from stable + keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + } - var combo = container.OfType().FirstOrDefault(); + var combo = container.OfType().FirstOrDefault(); - if (combo != null) - { - combo.Anchor = Anchor.BottomLeft; - combo.Origin = Anchor.BottomLeft; - combo.Scale = new Vector2(1.28f); - } - }) - { - Children = new Drawable[] - { - new LegacyDefaultComboCounter(), - new LegacyKeyCounterDisplay(), - } - }; + if (combo != null) + { + combo.Anchor = Anchor.BottomLeft; + combo.Origin = Anchor.BottomLeft; + combo.Scale = new Vector2(1.28f); + } + }) + { + Children = new Drawable[] + { + new LegacyDefaultComboCounter(), + new LegacyKeyCounterDisplay(), + } + }; + } + + return null; case OsuSkinComponentLookup osuComponent: switch (osuComponent.Component) { case OsuSkinComponents.FollowPoint: - return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); + return this.GetAnimation("followpoint", true, true, true, startAtCurrentTime: false, + maxSize: new Vector2(OsuHitObject.OBJECT_RADIUS * 2, OsuHitObject.OBJECT_RADIUS)); case OsuSkinComponents.SliderScorePoint: return this.GetAnimation("sliderscorepoint", false, false, maxSize: OsuHitObject.OBJECT_DIMENSIONS); From 306e84c7aca8ac5dc89be8fb0f15941902ac05f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 10:46:58 +0200 Subject: [PATCH 591/670] Move disposal method to more expected location --- .../OnlinePlay/DailyChallenge/DailyChallengeIntro.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs index 83ea0f8088..e59031f663 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengeIntro.cs @@ -367,12 +367,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge base.OnSuspending(e); } - protected override void Dispose(bool isDisposing) - { - resetAudio(); - base.Dispose(isDisposing); - } - private void updateAnimationState() { if (!beatmapBackgroundLoaded || !this.IsCurrentScreen()) @@ -500,6 +494,12 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge beatmapImpactChannel?.Play(); } + protected override void Dispose(bool isDisposing) + { + resetAudio(); + base.Dispose(isDisposing); + } + private void resetAudio() { dateWindupChannel?.Stop(); From 041c70e4ebb8065ef5ab0b866ad17991c345319a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:19:02 +0200 Subject: [PATCH 592/670] Fix tests --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 1 + .../Visual/UserInterface/TestSceneMainMenuButton.cs | 10 ++++++++-- osu.Game/Screens/Menu/DailyChallengeButton.cs | 4 +++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 613d8347b7..aab3716463 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -48,6 +48,7 @@ namespace osu.Game.Tests.Visual.Menus { new PlaylistItem(beatmap) }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) }, EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) } }); return true; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs index af98aa21db..98f2b129ff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs @@ -58,6 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface { new PlaylistItem(beatmap) }, + StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) }, EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) } }); return true; @@ -95,8 +96,13 @@ namespace osu.Game.Tests.Visual.UserInterface }, }; }); - AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); + AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); + AddStep("clear notifications", () => + { + foreach (var notification in notificationOverlay.AllNotifications) + notification.Close(runFlingAnimation: false); + }); AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null)); AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); @@ -105,7 +111,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RoomID = 1234, })); - AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1)); + AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero); } } } diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index ac04afdc4d..2357c60f3d 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -154,7 +154,9 @@ namespace osu.Game.Screens.Menu cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; // We only want to notify the user if a new challenge recently went live. - if (Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) + if (room.StartDate.Value != null + && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 + && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) { lastNotifiedDailyChallengeRoomId = room.RoomID.Value; notificationOverlay?.Post(new NewDailyChallengeNotification(room)); From 54a1d791362c31da283ca3afd33a3841e440e92e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:23:36 +0200 Subject: [PATCH 593/670] Clean up some naming weirdness --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 2357c60f3d..234c4c27d5 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -132,21 +132,21 @@ namespace osu.Game.Screens.Menu private long? lastNotifiedDailyChallengeRoomId; - private void dailyChallengeChanged(ValueChangedEvent info) + private void dailyChallengeChanged(ValueChangedEvent _) { UpdateState(); scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate = null; - if (this.info.Value == null) + if (info.Value == null) { Room = null; cover.OnlineInfo = TooltipContent = null; } else { - var roomRequest = new GetRoomRequest(this.info.Value.Value.RoomID); + var roomRequest = new GetRoomRequest(info.Value.Value.RoomID); roomRequest.Success += room => { From 96bd374b18fa12df7545f0aeca4660147e0feec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 12 Aug 2024 11:24:59 +0200 Subject: [PATCH 594/670] Change notification interval to 30 minutes --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index 234c4c27d5..e19ba6612c 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Menu // We only want to notify the user if a new challenge recently went live. if (room.StartDate.Value != null - && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 600 + && Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800 && room.RoomID.Value != lastNotifiedDailyChallengeRoomId) { lastNotifiedDailyChallengeRoomId = room.RoomID.Value; From b567ab2a3923d6579cf23849447546e704a621fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Aug 2024 20:28:21 +0900 Subject: [PATCH 595/670] Fix context menus sometimes not being clickable at song select Closes https://github.com/ppy/osu/issues/21602. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +-- osu.Game/Screens/Select/SongSelect.cs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cd0d2eea2c..b0f198d486 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -22,7 +22,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osuTK; @@ -209,7 +208,7 @@ namespace osu.Game.Screens.Select public BeatmapCarousel() { root = new CarouselRoot(this); - InternalChild = new OsuContextMenuContainer + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 307043a312..2ee5a6f3cb 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -26,6 +26,7 @@ using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -1081,7 +1082,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.Centre; Origin = Anchor.Centre; Width = panel_overflow; // avoid horizontal masking so the panels don't clip when screen stack is pushed. - InternalChild = Content = new Container + InternalChild = Content = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, From 7cbb4ab6f1c689f7f57d1ca6f046d4b49cdd1d65 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 13 Aug 2024 11:50:33 +0800 Subject: [PATCH 596/670] Get in-game locale for wiki pages --- osu.Game/Overlays/WikiOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index ffbc168fb7..88450ea6db 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Extensions; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -38,11 +39,20 @@ namespace osu.Game.Overlays private WikiArticlePage articlePage; + private Bindable language; + public WikiOverlay() : base(OverlayColourScheme.Orange, false) { } + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + // Fetch current language on load for translated pages (if possible) + language = game.CurrentLanguage.GetBoundCopy(); + } + public void ShowPage(string pagePath = INDEX_PATH) { path.Value = pagePath.Trim('/'); @@ -113,12 +123,13 @@ namespace osu.Game.Overlays cancellationToken?.Cancel(); request?.Cancel(); + // Language code + path, or just path1 + path2 in case string[] values = e.NewValue.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var language)) - request = new GetWikiRequest(values[1], language); + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var lang)) + request = new GetWikiRequest(values[1], lang); else - request = new GetWikiRequest(e.NewValue); + request = new GetWikiRequest(e.NewValue, language.Value); Loading.Show(); From 12a1889fac182cf521dc87693fb9a6c0e7ecb636 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 14:03:26 +0900 Subject: [PATCH 597/670] Use key offsets instead of cast --- osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index a5cc94ea9a..5d4cebca30 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -38,11 +38,11 @@ namespace osu.Game.Rulesets.Mania.Replays switch (point) { case HitPoint: - actions.Add((ManiaAction)point.Column); + actions.Add(ManiaAction.Key1 + point.Column); break; case ReleasePoint: - actions.Remove((ManiaAction)point.Column); + actions.Remove(ManiaAction.Key1 + point.Column); break; } } From ca91726190636314d46b9bddd6f865dd870781a2 Mon Sep 17 00:00:00 2001 From: CloneWith Date: Tue, 13 Aug 2024 13:34:11 +0800 Subject: [PATCH 598/670] Reload wiki page on language change --- osu.Game/Overlays/WikiOverlay.cs | 39 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 88450ea6db..0587832533 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -68,7 +68,9 @@ namespace osu.Game.Overlays protected override void LoadComplete() { base.LoadComplete(); + path.BindValueChanged(onPathChanged); + language.BindValueChanged(onLangChanged); wikiData.BindTo(Header.WikiPageData); } @@ -110,26 +112,18 @@ namespace osu.Game.Overlays } } - private void onPathChanged(ValueChangedEvent e) + private void loadPage(string path, Language lang) { - // the path could change as a result of redirecting to a newer location of the same page. - // we already have the correct wiki data, so we can safely return here. - if (e.NewValue == wikiData.Value?.Path) - return; - - if (e.NewValue == "error") - return; - cancellationToken?.Cancel(); request?.Cancel(); // Language code + path, or just path1 + path2 in case - string[] values = e.NewValue.Split('/', 2); + string[] values = path.Split('/', 2); - if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var lang)) - request = new GetWikiRequest(values[1], lang); + if (values.Length > 1 && LanguageExtensions.TryParseCultureCode(values[0], out var parsedLang)) + request = new GetWikiRequest(values[1], parsedLang); else - request = new GetWikiRequest(e.NewValue, language.Value); + request = new GetWikiRequest(path, lang); Loading.Show(); @@ -143,6 +137,25 @@ namespace osu.Game.Overlays api.PerformAsync(request); } + private void onPathChanged(ValueChangedEvent e) + { + // the path could change as a result of redirecting to a newer location of the same page. + // we already have the correct wiki data, so we can safely return here. + if (e.NewValue == wikiData.Value?.Path) + return; + + if (e.NewValue == "error") + return; + + loadPage(e.NewValue, language.Value); + } + + private void onLangChanged(ValueChangedEvent e) + { + // Path unmodified, just reload the page with new language value. + loadPage(path.Value, e.NewValue); + } + private void onSuccess(APIWikiPage response) { wikiData.Value = response; From 952a73b005e83eaea0167790462d78447eeba195 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 15:51:33 +0900 Subject: [PATCH 599/670] Make `ManiaAction` start at `1` --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 36ccf68d76..9c58139c37 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania public enum ManiaAction { [Description("Key 1")] - Key1, + Key1 = 1, [Description("Key 2")] Key2, From 3f02869bcc7455357910f716aa1f5841fda7c2ac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 16:08:06 +0900 Subject: [PATCH 600/670] Enable NRT while we're here --- osu.Game/Overlays/WikiOverlay.cs | 40 +++++++++++++------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 0587832533..14a25a909d 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Threading; @@ -25,32 +23,35 @@ namespace osu.Game.Overlays public string CurrentPath => path.Value; private readonly Bindable path = new Bindable(INDEX_PATH); - - private readonly Bindable wikiData = new Bindable(); + private readonly Bindable wikiData = new Bindable(); + private readonly IBindable language = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private GetWikiRequest request; + [Resolved] + private OsuGameBase game { get; set; } = null!; - private CancellationTokenSource cancellationToken; + private GetWikiRequest? request; + private CancellationTokenSource? cancellationToken; + private WikiArticlePage? articlePage; private bool displayUpdateRequired = true; - private WikiArticlePage articlePage; - - private Bindable language; - public WikiOverlay() : base(OverlayColourScheme.Orange, false) { } - [BackgroundDependencyLoader] - private void load(OsuGameBase game) + protected override void LoadComplete() { - // Fetch current language on load for translated pages (if possible) - language = game.CurrentLanguage.GetBoundCopy(); + base.LoadComplete(); + + path.BindValueChanged(onPathChanged); + wikiData.BindTo(Header.WikiPageData); + + language.BindTo(game.CurrentLanguage); + language.BindValueChanged(onLangChanged); } public void ShowPage(string pagePath = INDEX_PATH) @@ -65,15 +66,6 @@ namespace osu.Game.Overlays ShowParentPage = showParentPage, }; - protected override void LoadComplete() - { - base.LoadComplete(); - - path.BindValueChanged(onPathChanged); - language.BindValueChanged(onLangChanged); - wikiData.BindTo(Header.WikiPageData); - } - protected override void PopIn() { base.PopIn(); From 4f37643780d18049a384917debbe3ceed2c8d926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 16:38:56 +0900 Subject: [PATCH 601/670] Revert "Make `ManiaAction` start at `1`" This reverts commit 952a73b005e83eaea0167790462d78447eeba195. --- osu.Game.Rulesets.Mania/ManiaInputManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 9c58139c37..36ccf68d76 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania public enum ManiaAction { [Description("Key 1")] - Key1 = 1, + Key1, [Description("Key 2")] Key2, From 58354e3e6841b2a2c44be1cc34a0dfdb40455151 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 17:18:11 +0900 Subject: [PATCH 602/670] Fix another test The last two PRs didn't interact well together. --- osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 070ade4ad9..713f2f3fb1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank); Assert.That(score.Replay.Frames, Has.One.Matches(frame => - frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key16 }))); + frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key18 }))); } } From 14a00621f8280a82449e843dd1817f06889e391f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 17:28:16 +0900 Subject: [PATCH 603/670] Fix occasional test failures in `TestSceneBetmapRecommendations` The game was being constructed befor the API was setup, which could mean depending on test execution ordering and speed, the recommendations array would not be filled. Easy to reproduce by `[Solo]`ing `TestCorrectStarRatingIsUsed`. See https://github.com/ppy/osu/runs/28689915929#r0s0. --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index a368e901f5..16c8bc1a6b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -31,8 +31,6 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUpSteps] public override void SetUpSteps() { - base.SetUpSteps(); - AddStep("populate ruleset statistics", () => { Dictionary rulesetStatistics = new Dictionary(); @@ -68,6 +66,8 @@ namespace osu.Game.Tests.Visual.SongSelect return 0; } } + + base.SetUpSteps(); } [Test] From 93c1a27f226101b37685ff67166d01d71982f43e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 18:34:15 +0900 Subject: [PATCH 604/670] Add failing test --- osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index b03fa00f76..3eff7ca017 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -248,7 +248,8 @@ namespace osu.Game.Rulesets.Catch.Tests { AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true)); AddStep("catch fruit", () => attemptCatch(new Fruit())); - AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value); + AddAssert("correct hit lighting colour", + () => catcher.ChildrenOfType().First()?.Entry?.ObjectColour == this.ChildrenOfType().First().AccentColour.Value); } [Test] @@ -259,6 +260,16 @@ namespace osu.Game.Rulesets.Catch.Tests AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any()); } + [Test] + public void TestAllExplodedObjectsAtUniquePositions() + { + AddStep("catch normal fruit", () => attemptCatch(new Fruit())); + AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true })); + AddAssert("two fruit at distinct x coordinates", + () => this.ChildrenOfType().Select(f => f.DrawPosition.X).Distinct(), + () => Has.Exactly(2).Items); + } + private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count); private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state); From fbfe3a488744fea1137f315fd518c17e5a6d5c8a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 18:07:52 +0900 Subject: [PATCH 605/670] Fix fruit positions getting mangled when exploded --- .../Objects/Drawables/CaughtObject.cs | 28 ++++++------- .../DrawablePalpableCatchHitObject.cs | 4 ++ .../Objects/Drawables/IHasCatchObjectState.cs | 34 +++++++++++---- osu.Game.Rulesets.Catch/UI/Catcher.cs | 42 ++++++++++++------- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs index 0c26c52171..b76e0e9bae 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs @@ -21,11 +21,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public Bindable AccentColour { get; } = new Bindable(); public Bindable HyperDash { get; } = new Bindable(); public Bindable IndexInBeatmap { get; } = new Bindable(); - + public Vector2 DisplayPosition => DrawPosition; public Vector2 DisplaySize => Size * Scale; - public float DisplayRotation => Rotation; - public double DisplayStartTime => HitObject.StartTime; /// @@ -44,19 +42,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2); } - /// - /// Copies the hit object visual state from another object. - /// - public virtual void CopyStateFrom(IHasCatchObjectState objectState) - { - HitObject = objectState.HitObject; - Scale = Vector2.Divide(objectState.DisplaySize, Size); - Rotation = objectState.DisplayRotation; - AccentColour.Value = objectState.AccentColour.Value; - HyperDash.Value = objectState.HyperDash.Value; - IndexInBeatmap.Value = objectState.IndexInBeatmap.Value; - } - protected override void FreeAfterUse() { ClearTransforms(); @@ -64,5 +49,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.FreeAfterUse(); } + + public void RestoreState(CatchObjectState state) + { + HitObject = state.HitObject; + AccentColour.Value = state.AccentColour; + HyperDash.Value = state.HyperDash; + IndexInBeatmap.Value = state.IndexInBeatmap; + Position = state.DisplayPosition; + Scale = Vector2.Divide(state.DisplaySize, Size); + Rotation = state.DisplayRotation; + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index ade00918ab..2919f69966 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables /// protected readonly Container ScalingContainer; + public Vector2 DisplayPosition => DrawPosition; + public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale; public float DisplayRotation => ScalingContainer.Rotation; @@ -95,5 +97,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables base.OnFree(); } + + public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject."); } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs index 18fc0db6e3..e4a67d8fbf 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs @@ -13,17 +13,37 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public interface IHasCatchObjectState { PalpableCatchHitObject HitObject { get; } - - double DisplayStartTime { get; } - Bindable AccentColour { get; } - Bindable HyperDash { get; } - Bindable IndexInBeatmap { get; } - + double DisplayStartTime { get; } + Vector2 DisplayPosition { get; } Vector2 DisplaySize { get; } - float DisplayRotation { get; } + + void RestoreState(CatchObjectState state); } + + public static class HasCatchObjectStateExtensions + { + public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState( + target.HitObject, + target.AccentColour.Value, + target.HyperDash.Value, + target.IndexInBeatmap.Value, + target.DisplayStartTime, + target.DisplayPosition, + target.DisplaySize, + target.DisplayRotation); + } + + public readonly record struct CatchObjectState( + PalpableCatchHitObject HitObject, + Color4 AccentColour, + bool HyperDash, + int IndexInBeatmap, + double DisplayStartTime, + Vector2 DisplayPosition, + Vector2 DisplaySize, + float DisplayRotation); } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index dca01fc61a..6a1b251d60 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Buffers; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Catch.UI if (caughtObject == null) return; - caughtObject.CopyStateFrom(drawableObject); + caughtObject.RestoreState(drawableObject.SaveState()); caughtObject.Anchor = Anchor.TopCentre; caughtObject.Position = position; caughtObject.Scale *= caught_fruit_scale_adjust; @@ -411,41 +412,50 @@ namespace osu.Game.Rulesets.Catch.UI } } - private CaughtObject getDroppedObject(CaughtObject caughtObject) + private CaughtObject getDroppedObject(CatchObjectState state) { - var droppedObject = getCaughtObject(caughtObject.HitObject); + var droppedObject = getCaughtObject(state.HitObject); Debug.Assert(droppedObject != null); - droppedObject.CopyStateFrom(caughtObject); + droppedObject.RestoreState(state); droppedObject.Anchor = Anchor.TopLeft; - droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget); + droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget); return droppedObject; } private void clearPlate(DroppedObjectAnimation animation) { - var caughtObjects = caughtObjectContainer.Children.ToArray(); + int caughtCount = caughtObjectContainer.Children.Count; + CatchObjectState[] states = ArrayPool.Shared.Rent(caughtCount); - caughtObjectContainer.Clear(false); + try + { + for (int i = 0; i < caughtCount; i++) + states[i] = caughtObjectContainer.Children[i].SaveState(); - // Use the already returned PoolableDrawables for new objects - var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray(); + caughtObjectContainer.Clear(false); - droppedObjectTarget.AddRange(droppedObjects); - - foreach (var droppedObject in droppedObjects) - applyDropAnimation(droppedObject, animation); + for (int i = 0; i < caughtCount; i++) + { + CaughtObject obj = getDroppedObject(states[i]); + droppedObjectTarget.Add(obj); + applyDropAnimation(obj, animation); + } + } + finally + { + ArrayPool.Shared.Return(states); + } } private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation) { + CatchObjectState state = caughtObject.SaveState(); caughtObjectContainer.Remove(caughtObject, false); - var droppedObject = getDroppedObject(caughtObject); - + var droppedObject = getDroppedObject(state); droppedObjectTarget.Add(droppedObject); - applyDropAnimation(droppedObject, animation); } From c9b2a5bb9cc86d22d3731ad3375a734e36dcf219 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Aug 2024 13:01:50 +0300 Subject: [PATCH 606/670] Fix user profile overlay colour resetting when changing rulesets --- osu.Game/Overlays/UserProfileOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index ac1fc44cd6..076905819e 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -96,7 +96,8 @@ namespace osu.Game.Overlays { Debug.Assert(user != null); - if (user.OnlineID == Header.User.Value?.User.Id && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) + bool sameUser = user.OnlineID == Header.User.Value?.User.Id; + if (sameUser && ruleset?.MatchesOnlineID(Header.User.Value?.Ruleset) == true) return; if (sectionsContainer != null) @@ -118,7 +119,9 @@ namespace osu.Game.Overlays } : Array.Empty(); - changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + if (!sameUser) + changeOverlayColours(OverlayColourScheme.Pink.GetHue()); + recreateBaseContent(); if (API.State.Value != APIState.Offline) From 7acc1772cbef1116160caabdba787015d9ac6bff Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 13 Aug 2024 13:07:21 +0300 Subject: [PATCH 607/670] Add test coverage --- .../Online/TestSceneUserProfileOverlay.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 3bb38f167f..006610dccd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -11,6 +11,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Rulesets.Taiko; using osu.Game.Users; namespace osu.Game.Tests.Visual.Online @@ -192,13 +193,26 @@ namespace osu.Game.Tests.Visual.Online int hue2 = 0; AddSliderStep("hue 2", 0, 360, 50, h => hue2 = h); - AddStep("show user", () => profile.ShowUser(new APIUser { Id = 1 })); + AddStep("show user", () => profile.ShowUser(new APIUser { Id = 2 })); AddWaitStep("wait some", 3); AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser { Username = $"Colorful #{hue2}", - Id = 1, + Id = 2, + CountryCode = CountryCode.JP, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", + ProfileHue = hue2, + PlayMode = "osu", + })); + + AddStep("show user different ruleset", () => profile.ShowUser(new APIUser { Id = 2 }, new TaikoRuleset().RulesetInfo)); + AddWaitStep("wait some", 3); + + AddStep("complete request", () => pendingRequest.TriggerSuccess(new APIUser + { + Username = $"Colorful #{hue2}", + Id = 2, CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg", ProfileHue = hue2, From 69c5e6a799175cdc781c30724368186f7e788580 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Aug 2024 19:52:14 +0900 Subject: [PATCH 608/670] Remove unused property causing CI inspection I don't see this in my Rider locally. I suppose we can remove it, though it was intentionally added so that the struct mirrors the interface. --- .../Objects/Drawables/IHasCatchObjectState.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs index e4a67d8fbf..19e66bf995 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs @@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables target.AccentColour.Value, target.HyperDash.Value, target.IndexInBeatmap.Value, - target.DisplayStartTime, target.DisplayPosition, target.DisplaySize, target.DisplayRotation); @@ -42,7 +41,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables Color4 AccentColour, bool HyperDash, int IndexInBeatmap, - double DisplayStartTime, Vector2 DisplayPosition, Vector2 DisplaySize, float DisplayRotation); From d74ac57092f3d1953345532010936d93f3beb052 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 19:26:24 +0900 Subject: [PATCH 609/670] Never call `prepareDrawables` from unsafe context I can't mentally figure out *what* is causing the issue here, but in the case where `prepareDrawables` is called from `JudgementBody.OnSkinChanged` (only happens in a non-pooled scenario), things go very wrong. I think a smell test is enough for anyone to agree that the flow was very bad. Removing this call doesn't seem to cause any issues. `runAnimation` should always be called in `PrepareForUse` (both pooled and non-pooled scenarios) so things should still always be in a correct state. Closes #29398. --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index bdeadfd201..1b12bfc945 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -119,9 +119,6 @@ namespace osu.Game.Rulesets.Judgements private void runAnimation() { - // is a no-op if the drawables are already in a correct state. - prepareDrawables(); - // undo any transforms applies in ApplyMissAnimations/ApplyHitAnimations to get a sane initial state. ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); From bb0c9e24974f2a8ea2b3c2954d2342f053a7da7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 19:30:29 +0900 Subject: [PATCH 610/670] Add log output when judgements aren't being pooled --- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 1b12bfc945..0e04cd3eec 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -112,6 +113,9 @@ namespace osu.Game.Rulesets.Judgements { base.PrepareForUse(); + if (!IsInPool) + Logger.Log($"{nameof(DrawableJudgement)} for judgement type {Result} was not retrieved from a pool. Consider adding to a JudgementPooler."); + Debug.Assert(Result != null); runAnimation(); From 2221c4891f3fa7e9d0b005adefee982ab6091f2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Aug 2024 21:03:00 +0900 Subject: [PATCH 611/670] Remove legacy non-pooled pathway to `DrawableJudgement` --- .../Skinning/TestSceneDrawableJudgement.cs | 16 +++++++++++----- .../UI/DrawableManiaJudgement.cs | 10 ---------- .../Rulesets/Judgements/DrawableJudgement.cs | 11 ----------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index c993ba0e0a..b52919987f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -28,14 +28,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning AddStep("Show " + result.GetDescription(), () => { SetContents(_ => - new DrawableManiaJudgement(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) - { - Type = result - }, null) + { + var drawableManiaJudgement = new DrawableManiaJudgement { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); + }; + + drawableManiaJudgement.Apply(new JudgementResult(new HitObject { StartTime = Time.Current }, new Judgement()) + { + Type = result + }, null); + + return drawableManiaJudgement; + }); // for test purposes, undo the Y adjustment related to the `ScorePosition` legacy positioning config value // (see `LegacyManiaJudgementPiece.load()`). diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index 896dfb2b23..9f25a44e21 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -5,22 +5,12 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.UI { public partial class DrawableManiaJudgement : DrawableJudgement { - public DrawableManiaJudgement(JudgementResult result, DrawableHitObject judgedObject) - : base(result, judgedObject) - { - } - - public DrawableManiaJudgement() - { - } - protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result); private partial class DefaultManiaJudgementPiece : DefaultJudgementPiece diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 0e04cd3eec..8c326ecf49 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -36,17 +36,6 @@ namespace osu.Game.Rulesets.Judgements private readonly Lazy proxiedAboveHitObjectsContent; public Drawable ProxiedAboveHitObjectsContent => proxiedAboveHitObjectsContent.Value; - /// - /// Creates a drawable which visualises a . - /// - /// The judgement to visualise. - /// The object which was judged. - public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject) - : this() - { - Apply(result, judgedObject); - } - public DrawableJudgement() { Size = new Vector2(judgement_size); From 78ef436ea085c1e08258cf46a0610677af729af4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 12:23:47 +0900 Subject: [PATCH 612/670] Update test debug output to test second scenario --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index ee2f1d64dc..d2c69c2ceb 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -788,7 +788,7 @@ namespace osu.Game.Online.Multiplayer try { - indexOf = Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID)); + indexOf = APIRoom!.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); Room.Playlist[indexOf] = item; } catch From dd9705b660d54d75ee8db01269aed840d6c93bb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 12:26:21 +0900 Subject: [PATCH 613/670] Fix file access test failure by forcing retries See https://github.com/ppy/osu/actions/runs/10369630825/job/28708248682. --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 3ae1d9786d..2b23581984 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -47,6 +47,7 @@ using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Utils; using osuTK; using osuTK.Input; using SharpCompress; @@ -240,11 +241,14 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("change beatmap files", () => { - foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + FileUtils.AttemptOperation(() => { - using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite)) - stream.WriteByte(0); - } + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + { + using (var stream = Game.Storage.GetStream(Path.Combine("files", file.File.GetStoragePath()), FileAccess.ReadWrite)) + stream.WriteByte(0); + } + }); }); AddStep("invalidate cache", () => From f882ad4a53c4beb0521f57cadd7e969e753b69b6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Aug 2024 15:10:55 +0900 Subject: [PATCH 614/670] Add localisation for daily challenge day/week units --- .../DailyChallengeStatsDisplayStrings.cs | 24 +++++++++++++++++++ .../Components/DailyChallengeStatsDisplay.cs | 4 ++-- .../Components/DailyChallengeStatsTooltip.cs | 13 +++++----- 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs diff --git a/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs b/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs new file mode 100644 index 0000000000..2ef5e45c92 --- /dev/null +++ b/osu.Game/Localisation/DailyChallengeStatsDisplayStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class DailyChallengeStatsDisplayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.DailyChallengeStatsDisplay"; + + /// + /// "{0}d" + /// + public static LocalisableString UnitDay(LocalisableString count) => new TranslatableString(getKey(@"unit_day"), @"{0}d", count); + + /// + /// "{0}w" + /// + public static LocalisableString UnitWeek(LocalisableString count) => new TranslatableString(getKey(@"unit_week"), @"{0}w", count); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs index f55eb595d7..82d3cfafd7 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsDisplay.cs @@ -12,8 +12,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; +using osu.Game.Localisation; namespace osu.Game.Overlays.Profile.Header.Components { @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Profile.Header.Components APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics; - dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0")); + dailyPlayCount.Text = DailyChallengeStatsDisplayStrings.UnitDay(stats.PlayCount.ToLocalisableString("N0")); dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount)); TooltipContent = new DailyChallengeTooltipData(colourProvider, stats); diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 1b54633b8a..64a8d67c5b 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -9,15 +9,16 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osuTK; -using Box = osu.Framework.Graphics.Shapes.Box; -using Color4 = osuTK.Graphics.Color4; +using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header.Components { @@ -112,16 +113,16 @@ namespace osu.Game.Overlays.Profile.Header.Components background.Colour = colourProvider.Background4; topBackground.Colour = colourProvider.Background5; - currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); + currentDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0")); currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent)); - currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0")); currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent)); - bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = DailyChallengeStatsDisplayStrings.UnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0")); bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest)); - bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = DailyChallengeStatsDisplayStrings.UnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0")); bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest)); topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0"); From 7c142bcedf0877a05b9f899b0648cac6b925d354 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 15:12:23 +0900 Subject: [PATCH 615/670] Fix incorrect anchor handling in `ArgonManiaComboCounter` --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index e77650bed1..43d4e89cdb 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -30,8 +30,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon private void updateAnchor() { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } // since we flip the vertical anchor when changing scroll direction, // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. From 46d41cb59083eaf60c3c5c2b35a05eea7aa9d55b Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 20:01:42 -0700 Subject: [PATCH 616/670] Add base song select components test scene --- .../SongSelect/TestSceneSongSelectV2.cs | 5 +-- .../TestSceneSongSelectV2Navigation.cs | 3 +- .../SongSelectComponentsTestScene.cs | 45 +++++++++++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs index 0a632793cc..02f503d433 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens; using osu.Game.Screens.Footer; using osu.Game.Screens.Menu; -using osu.Game.Screens.SelectV2; using osu.Game.Screens.SelectV2.Footer; using osuTK.Input; @@ -63,8 +62,8 @@ namespace osu.Game.Tests.Visual.SongSelect { base.SetUpSteps(); - AddStep("load screen", () => Stack.Push(new SongSelectV2())); - AddUntilStep("wait for load", () => Stack.CurrentScreen is SongSelectV2 songSelect && songSelect.IsLoaded); + AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2())); + AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded); } #region Footer diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs index ededb80228..0ca27c539a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Screens.Menu; -using osu.Game.Screens.SelectV2; using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect @@ -17,7 +16,7 @@ namespace osu.Game.Tests.Visual.SongSelect base.SetUpSteps(); AddStep("press enter", () => InputManager.Key(Key.Enter)); AddWaitStep("wait", 5); - PushAndConfirm(() => new SongSelectV2()); + PushAndConfirm(() => new Screens.SelectV2.SongSelectV2()); } [Test] diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs new file mode 100644 index 0000000000..ff81d72d12 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -0,0 +1,45 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public abstract partial class SongSelectComponentsTestScene : OsuTestScene + { + [Cached] + protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + /// + /// The beatmap. Can be local/online depending on the context. + /// + [Cached(typeof(IBindable))] + protected readonly Bindable BeatmapInfo = new Bindable(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + // mimics song select's `WorkingBeatmap` binding + Beatmap.BindValueChanged(b => + { + BeatmapInfo.Value = b.NewValue.BeatmapInfo; + }); + } + + [SetUpSteps] + public virtual void SetUpSteps() + { + AddStep("reset dependencies", () => + { + Beatmap.SetDefault(); + SelectedMods.SetDefault(); + BeatmapInfo.Value = null; + }); + } + } +} From 625c6fc7eb9c932d167ceb4b2679a1cb2e7758d4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 19:52:42 -0700 Subject: [PATCH 617/670] Implement song select v2 difficulty name content component --- .../TestSceneDifficultyNameContent.cs | 97 ++++++++++++++++++ .../SelectV2/Wedge/DifficultyNameContent.cs | 98 +++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs create mode 100644 osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs new file mode 100644 index 0000000000..884dae1617 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.SelectV2.Wedge; + +namespace osu.Game.Tests.Visual.SongSelectV2 +{ + public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene + { + private Container? content; + private DifficultyNameContent? difficultyNameContent; + private float relativeWidth; + + [BackgroundDependencyLoader] + private void load() + { + AddSliderStep("change relative width", 0, 1f, 0.5f, v => + { + if (content != null) + content.Width = v; + + relativeWidth = v; + }); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("set content", () => + { + Child = content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Child = difficultyNameContent = new DifficultyNameContent(), + } + } + }; + }); + } + + [Test] + public void TestLocalBeatmap() + { + AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); + AddAssert("author is not set", () => !difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + + AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + DifficultyName = "really long difficulty name that gets truncated", + Metadata = new BeatmapMetadata + { + Author = { Username = "really long username that is autosized" }, + }, + OnlineID = 1, + } + })); + + AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); + AddAssert("author is set", () => difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + } + + [Test] + public void TestNullBeatmap() + { + AddStep("set beatmap", () => BeatmapInfo.Value = null); + } + } +} diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs new file mode 100644 index 0000000000..1778246841 --- /dev/null +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -0,0 +1,98 @@ +// 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.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.SelectV2.Wedge +{ + public partial class DifficultyNameContent : CompositeDrawable + { + private OsuSpriteText difficultyName = null!; + private OsuSpriteText mappedByLabel = null!; + private LinkFlowContainer mapperName = null!; + + [Resolved] + private IBindable beatmapInfo { get; set; } = null!; + + public DifficultyNameContent() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + difficultyName = new TruncatingSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), + }, + mappedByLabel = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + // TODO: better null display? beatmap carousel panels also just show this text currently. + Text = " mapped by ", + Font = OsuFont.GetFont(size: 14), + }, + mapperName = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14)) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmapInfo.BindValueChanged(b => + { + difficultyName.Text = b.NewValue?.DifficultyName ?? string.Empty; + updateMapper(); + }, true); + } + + private void updateMapper() + { + mapperName.Clear(); + + switch (beatmapInfo.Value) + { + case BeatmapInfo localBeatmap: + // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) + mapperName.AddUserLink(localBeatmap.Metadata.Author); + break; + } + } + + protected override void Update() + { + base.Update(); + + // truncate difficulty name when width exceeds bounds, prioritizing mapper name display + difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth + - mapperName.DrawWidth, 0); + } + } +} From 2b41f71fd0d688627d8375e3f2ad28bf552a5ba2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 12 Aug 2024 23:31:00 -0700 Subject: [PATCH 618/670] Workaround single-frame layout issues with `{Link|Text|Fill}FlowContainer`s --- .../TestSceneDifficultyNameContent.cs | 4 +-- .../SelectV2/Wedge/DifficultyNameContent.cs | 35 ++++++++++++++++--- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index 884dae1617..75bbf8f32a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 public void TestLocalBeatmap() { AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); - AddAssert("author is not set", () => !difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap { @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 })); AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); - AddAssert("author is set", () => difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Any()); + AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); } [Test] diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index 1778246841..7df8b4a3cc 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -10,6 +10,10 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Localisation; +using osu.Game.Online; +using osu.Game.Online.Chat; +using osu.Game.Overlays; namespace osu.Game.Screens.SelectV2.Wedge { @@ -17,11 +21,15 @@ namespace osu.Game.Screens.SelectV2.Wedge { private OsuSpriteText difficultyName = null!; private OsuSpriteText mappedByLabel = null!; - private LinkFlowContainer mapperName = null!; + private OsuHoverContainer mapperLink = null!; + private OsuSpriteText mapperName = null!; [Resolved] private IBindable beatmapInfo { get; set; } = null!; + [Resolved] + private ILinkHandler? linkHandler { get; set; } + public DifficultyNameContent() { RelativeSizeAxes = Axes.X; @@ -52,11 +60,15 @@ namespace osu.Game.Screens.SelectV2.Wedge Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, - mapperName = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14)) + mapperLink = new MapperLink { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, + Child = mapperName = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14), + } }, } }; @@ -75,13 +87,14 @@ namespace osu.Game.Screens.SelectV2.Wedge private void updateMapper() { - mapperName.Clear(); + mapperName.Text = string.Empty; switch (beatmapInfo.Value) { case BeatmapInfo localBeatmap: // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) - mapperName.AddUserLink(localBeatmap.Metadata.Author); + mapperName.Text = localBeatmap.Metadata.Author.Username; + mapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, localBeatmap.Metadata.Author)); break; } } @@ -94,5 +107,19 @@ namespace osu.Game.Screens.SelectV2.Wedge difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth - mapperName.DrawWidth, 0); } + + /// + /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. + /// See https://github.com/ppy/osu-framework/issues/3369. + /// + private partial class MapperLink : OsuHoverContainer + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) + { + TooltipText = ContextMenuStrings.ViewProfile; + IdleColour = overlayColourProvider?.Light2 ?? colours.Blue; + } + } } } From f8796e3192ebdc792a3a825399e913dbb6298ef2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 22:15:10 -0700 Subject: [PATCH 619/670] Move resizing width and background logic to `SongSelectComponentsTestScene` --- .../SongSelectComponentsTestScene.cs | 45 ++++++++++++++++ .../TestSceneDifficultyNameContent.cs | 51 +------------------ 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index ff81d72d12..4548355992 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -3,6 +3,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -11,6 +14,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public abstract partial class SongSelectComponentsTestScene : OsuTestScene { + protected Container ComponentContainer = null!; + + private Container? resizeContainer; + private float relativeWidth; + [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -20,6 +28,18 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Cached(typeof(IBindable))] protected readonly Bindable BeatmapInfo = new Bindable(); + [BackgroundDependencyLoader] + private void load() + { + AddSliderStep("change relative width", 0, 1f, 0.5f, v => + { + if (resizeContainer != null) + resizeContainer.Width = v; + + relativeWidth = v; + }); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -40,6 +60,31 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectedMods.SetDefault(); BeatmapInfo.Value = null; }); + + AddStep("set content", () => + { + Child = resizeContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + ComponentContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + } + } + }; + }); } } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index 75bbf8f32a..b556268be0 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -3,10 +3,6 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -18,56 +14,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene { - private Container? content; private DifficultyNameContent? difficultyNameContent; - private float relativeWidth; - - [BackgroundDependencyLoader] - private void load() - { - AddSliderStep("change relative width", 0, 1f, 0.5f, v => - { - if (content != null) - content.Width = v; - - relativeWidth = v; - }); - } - - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("set content", () => - { - Child = content = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Width = relativeWidth, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Child = difficultyNameContent = new DifficultyNameContent(), - } - } - }; - }); - } [Test] public void TestLocalBeatmap() { + AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new DifficultyNameContent()); + AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); From c24f1444f9ee854a4c0d2cc5eb5ada9ab18fd743 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 22:21:26 -0700 Subject: [PATCH 620/670] Directly resolve `IBindable` by making a local variant of `DifficultyNameContent` --- .../SongSelectComponentsTestScene.cs | 20 ------- .../TestSceneDifficultyNameContent.cs | 8 +-- .../SelectV2/Wedge/DifficultyNameContent.cs | 57 ++++--------------- .../Wedge/LocalDifficultyNameContent.cs | 34 +++++++++++ 4 files changed, 46 insertions(+), 73 deletions(-) create mode 100644 osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index 4548355992..d984a3a11a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -2,12 +2,10 @@ // 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.Shapes; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.SongSelectV2 @@ -22,12 +20,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Cached] protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - /// - /// The beatmap. Can be local/online depending on the context. - /// - [Cached(typeof(IBindable))] - protected readonly Bindable BeatmapInfo = new Bindable(); - [BackgroundDependencyLoader] private void load() { @@ -40,17 +32,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 }); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // mimics song select's `WorkingBeatmap` binding - Beatmap.BindValueChanged(b => - { - BeatmapInfo.Value = b.NewValue.BeatmapInfo; - }); - } - [SetUpSteps] public virtual void SetUpSteps() { @@ -58,7 +39,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { Beatmap.SetDefault(); SelectedMods.SetDefault(); - BeatmapInfo.Value = null; }); AddStep("set content", () => diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index b556268be0..e32d6ddb80 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Test] public void TestLocalBeatmap() { - AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new DifficultyNameContent()); + AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new LocalDifficultyNameContent()); AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); @@ -40,11 +40,5 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); } - - [Test] - public void TestNullBeatmap() - { - AddStep("set beatmap", () => BeatmapInfo.Value = null); - } } } diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index 7df8b4a3cc..f49714bee8 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -3,34 +3,24 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; -using osu.Game.Online; -using osu.Game.Online.Chat; using osu.Game.Overlays; namespace osu.Game.Screens.SelectV2.Wedge { - public partial class DifficultyNameContent : CompositeDrawable + public abstract partial class DifficultyNameContent : CompositeDrawable { - private OsuSpriteText difficultyName = null!; + protected OsuSpriteText DifficultyName = null!; private OsuSpriteText mappedByLabel = null!; - private OsuHoverContainer mapperLink = null!; - private OsuSpriteText mapperName = null!; + protected OsuHoverContainer MapperLink = null!; + protected OsuSpriteText MapperName = null!; - [Resolved] - private IBindable beatmapInfo { get; set; } = null!; - - [Resolved] - private ILinkHandler? linkHandler { get; set; } - - public DifficultyNameContent() + protected DifficultyNameContent() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -46,7 +36,7 @@ namespace osu.Game.Screens.SelectV2.Wedge Direction = FillDirection.Horizontal, Children = new Drawable[] { - difficultyName = new TruncatingSpriteText + DifficultyName = new TruncatingSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -60,12 +50,12 @@ namespace osu.Game.Screens.SelectV2.Wedge Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, - mapperLink = new MapperLink + MapperLink = new MapperLinkContainer { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, - Child = mapperName = new OsuSpriteText + Child = MapperName = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 14), } @@ -74,45 +64,20 @@ namespace osu.Game.Screens.SelectV2.Wedge }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - beatmapInfo.BindValueChanged(b => - { - difficultyName.Text = b.NewValue?.DifficultyName ?? string.Empty; - updateMapper(); - }, true); - } - - private void updateMapper() - { - mapperName.Text = string.Empty; - - switch (beatmapInfo.Value) - { - case BeatmapInfo localBeatmap: - // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) - mapperName.Text = localBeatmap.Metadata.Author.Username; - mapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, localBeatmap.Metadata.Author)); - break; - } - } - protected override void Update() { base.Update(); // truncate difficulty name when width exceeds bounds, prioritizing mapper name display - difficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth - - mapperName.DrawWidth, 0); + DifficultyName.MaxWidth = Math.Max(DrawWidth - mappedByLabel.DrawWidth + - MapperName.DrawWidth, 0); } /// /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. /// See https://github.com/ppy/osu-framework/issues/3369. /// - private partial class MapperLink : OsuHoverContainer + private partial class MapperLinkContainer : OsuHoverContainer { [BackgroundDependencyLoader] private void load(OverlayColourProvider? overlayColourProvider, OsuColour colours) diff --git a/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs new file mode 100644 index 0000000000..66f8cb02b2 --- /dev/null +++ b/osu.Game/Screens/SelectV2/Wedge/LocalDifficultyNameContent.cs @@ -0,0 +1,34 @@ +// 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.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Online.Chat; + +namespace osu.Game.Screens.SelectV2.Wedge +{ + public partial class LocalDifficultyNameContent : DifficultyNameContent + { + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private ILinkHandler? linkHandler { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.BindValueChanged(b => + { + DifficultyName.Text = b.NewValue.BeatmapInfo.DifficultyName; + + // TODO: should be the mapper of the guest difficulty, but that isn't stored correctly yet (see https://github.com/ppy/osu/issues/12965) + MapperName.Text = b.NewValue.Metadata.Author.Username; + MapperLink.Action = () => linkHandler?.HandleLink(new LinkDetails(LinkAction.OpenUserProfile, b.NewValue.Metadata.Author)); + }, true); + } + } +} From 21745105445fc4d540b2c93f1fd70f8c223337ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 15:51:07 +0900 Subject: [PATCH 621/670] Move other V2 tests to new test namespace --- .../{SongSelect => SongSelectV2}/TestSceneBeatmapInfoWedgeV2.cs | 2 +- .../{SongSelect => SongSelectV2}/TestSceneLeaderboardScoreV2.cs | 2 +- .../{SongSelect => SongSelectV2}/TestSceneSongSelectV2.cs | 2 +- .../TestSceneSongSelectV2Navigation.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneBeatmapInfoWedgeV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneLeaderboardScoreV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneSongSelectV2.cs (99%) rename osu.Game.Tests/Visual/{SongSelect => SongSelectV2}/TestSceneSongSelectV2Navigation.cs (95%) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs index 2a3269ea0a..aad2bd6334 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs @@ -18,7 +18,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Select; using osuTK; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { [TestFixture] public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs index 33af4907a1..4a733b2cbe 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboardScoreV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs @@ -24,7 +24,7 @@ using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneLeaderboardScoreV2 : OsuTestScene { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs similarity index 99% rename from osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs index 02f503d433..c93c41d558 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs @@ -20,7 +20,7 @@ using osu.Game.Screens.Menu; using osu.Game.Screens.SelectV2.Footer; using osuTK.Input; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneSongSelectV2 : ScreenTestScene { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs similarity index 95% rename from osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs index 0ca27c539a..a72146352e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs @@ -7,7 +7,7 @@ using osu.Framework.Testing; using osu.Game.Screens.Menu; using osuTK.Input; -namespace osu.Game.Tests.Visual.SongSelect +namespace osu.Game.Tests.Visual.SongSelectV2 { public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene { From 11bd0c9a6141a61023cdb43e1f4d3755d55c5c21 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Aug 2024 00:41:43 -0700 Subject: [PATCH 622/670] Inline single-frame layout issue comment instead --- osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs index f49714bee8..4a3dc34cf9 100644 --- a/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs +++ b/osu.Game/Screens/SelectV2/Wedge/DifficultyNameContent.cs @@ -50,6 +50,8 @@ namespace osu.Game.Screens.SelectV2.Wedge Text = " mapped by ", Font = OsuFont.GetFont(size: 14), }, + // This is not a `LinkFlowContainer` as there are single-frame layout issues when Update() + // is being used for layout, see https://github.com/ppy/osu-framework/issues/3369. MapperLink = new MapperLinkContainer { Anchor = Anchor.BottomLeft, @@ -73,10 +75,6 @@ namespace osu.Game.Screens.SelectV2.Wedge - MapperName.DrawWidth, 0); } - /// - /// This class is a workaround for the single-frame layout issues with `{Link|Text|Fill}FlowContainer`s. - /// See https://github.com/ppy/osu-framework/issues/3369. - /// private partial class MapperLinkContainer : OsuHoverContainer { [BackgroundDependencyLoader] From 6f2bc7e6f12350e66c4c5679d33d0e7db1490484 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 14 Aug 2024 00:44:03 -0700 Subject: [PATCH 623/670] Use `Content` override instead --- .../SongSelectComponentsTestScene.cs | 26 +++++++++---------- .../TestSceneDifficultyNameContent.cs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index d984a3a11a..1583d229c5 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -12,14 +12,19 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { public abstract partial class SongSelectComponentsTestScene : OsuTestScene { - protected Container ComponentContainer = null!; + [Cached] + protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + protected override Container Content { get; } = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + }; private Container? resizeContainer; private float relativeWidth; - [Cached] - protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - [BackgroundDependencyLoader] private void load() { @@ -41,9 +46,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 SelectedMods.SetDefault(); }); - AddStep("set content", () => + AddStep("setup content", () => { - Child = resizeContainer = new Container + base.Content.Add(resizeContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -56,14 +61,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2 RelativeSizeAxes = Axes.Both, Colour = ColourProvider.Background5, }, - ComponentContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - } + Content } - }; + }); }); } } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs index e32d6ddb80..49e7e2bc1a 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneDifficultyNameContent.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Test] public void TestLocalBeatmap() { - AddStep("set component", () => ComponentContainer.Child = difficultyNameContent = new LocalDifficultyNameContent()); + AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent()); AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().Text)); AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType().Single().ChildrenOfType().Single().Text)); From 28ab65243d0159ba9ea7015ef58d7916c53c61e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 20:45:27 +0900 Subject: [PATCH 624/670] Remove daily challenge tooltip from main menu Now that we have a nice intro screen for the daily challenge, it's generally thought that we want to "spoil" the beatmap until the intro is shown. Also I was never a huge fan of having a tooltip on a main menu button.. just feels a bit odd. --- osu.Game/Screens/Menu/DailyChallengeButton.cs | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/osu.Game/Screens/Menu/DailyChallengeButton.cs b/osu.Game/Screens/Menu/DailyChallengeButton.cs index e19ba6612c..e6593c9b0d 100644 --- a/osu.Game/Screens/Menu/DailyChallengeButton.cs +++ b/osu.Game/Screens/Menu/DailyChallengeButton.cs @@ -9,12 +9,10 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps.Drawables; -using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Localisation; @@ -30,7 +28,7 @@ using osuTK.Input; namespace osu.Game.Screens.Menu { - public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip + public partial class DailyChallengeButton : MainMenuButton { public Room? Room { get; private set; } @@ -201,36 +199,6 @@ namespace osu.Game.Screens.Menu base.UpdateState(); } - public ITooltip GetCustomTooltip() => new DailyChallengeTooltip(); - public APIBeatmapSet? TooltipContent { get; private set; } - - internal partial class DailyChallengeTooltip : CompositeDrawable, ITooltip - { - [Cached] - private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - - private APIBeatmapSet? lastContent; - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - } - - public void Move(Vector2 pos) => Position = pos; - - public void SetContent(APIBeatmapSet? content) - { - if (content == lastContent) - return; - - lastContent = content; - - ClearInternal(); - if (content != null) - AddInternal(new BeatmapCardNano(content)); - } - } } } From 1665d9a93e03177295036ac68c1ca551ad95df89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 21:01:35 +0900 Subject: [PATCH 625/670] Fix failing test setup --- .../SongSelectComponentsTestScene.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index 1583d229c5..c7f1597051 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -28,6 +28,23 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [BackgroundDependencyLoader] private void load() { + base.Content.Child = resizeContainer = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Width = relativeWidth, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourProvider.Background5, + }, + Content + } + }; + AddSliderStep("change relative width", 0, 1f, 0.5f, v => { if (resizeContainer != null) @@ -45,26 +62,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 Beatmap.SetDefault(); SelectedMods.SetDefault(); }); - - AddStep("setup content", () => - { - base.Content.Add(resizeContainer = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), - Width = relativeWidth, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourProvider.Background5, - }, - Content - } - }); - }); } } } From e603888130dce54965411cda873d05c2a4b63de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Aug 2024 21:09:28 +0900 Subject: [PATCH 626/670] Update remaining tests to use new base class (and tidy up `V2` suffixes) --- .../SongSelectComponentsTestScene.cs | 2 +- ...edgeV2.cs => TestSceneBeatmapInfoWedge.cs} | 16 +++++++------- ...coreV2.cs => TestSceneLeaderboardScore.cs} | 21 ++----------------- ...SongSelectV2.cs => TestSceneSongSelect.cs} | 4 ++-- ...on.cs => TestSceneSongSelectNavigation.cs} | 2 +- 5 files changed, 14 insertions(+), 31 deletions(-) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneBeatmapInfoWedgeV2.cs => TestSceneBeatmapInfoWedge.cs} (97%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneLeaderboardScoreV2.cs => TestSceneLeaderboardScore.cs} (91%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneSongSelectV2.cs => TestSceneSongSelect.cs} (98%) rename osu.Game.Tests/Visual/SongSelectV2/{TestSceneSongSelectV2Navigation.cs => TestSceneSongSelectNavigation.cs} (92%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs index c7f1597051..b7b0101a7c 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/SongSelectComponentsTestScene.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 } }; - AddSliderStep("change relative width", 0, 1f, 0.5f, v => + AddSliderStep("change relative width", 0, 1f, 1f, v => { if (resizeContainer != null) resizeContainer.Width = v; diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs similarity index 97% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs index aad2bd6334..35bd4ee958 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedgeV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs @@ -20,8 +20,7 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - [TestFixture] - public partial class TestSceneBeatmapInfoWedgeV2 : OsuTestScene + public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene { private RulesetStore rulesets = null!; private TestBeatmapInfoWedgeV2 infoWedge = null!; @@ -33,6 +32,13 @@ namespace osu.Game.Tests.Visual.SongSelectV2 this.rulesets = rulesets; } + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("reset mods", () => SelectedMods.SetDefault()); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -107,12 +113,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist"); } - [SetUpSteps] - public void SetUpSteps() - { - AddStep("reset mods", () => SelectedMods.SetDefault()); - } - [Test] public void TestTruncation() { diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs similarity index 91% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs index 4a733b2cbe..a7d0d70c03 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScoreV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneLeaderboardScore.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -26,7 +25,7 @@ using osuTK; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneLeaderboardScoreV2 : OsuTestScene + public partial class TestSceneLeaderboardScore : SongSelectComponentsTestScene { [Cached] private OverlayColourProvider colourProvider { get; set; } = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -36,19 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 private FillFlowContainer? fillFlow; private OsuSpriteText? drawWidthText; - private float relativeWidth; - - [BackgroundDependencyLoader] - private void load() - { - // TODO: invalidation seems to be one-off when clicking slider to a certain value, so drag for now - // doesn't seem to happen in-game (when toggling window mode) - AddSliderStep("change relative width", 0, 1f, 0.6f, v => - { - relativeWidth = v; - if (fillFlow != null) fillFlow.Width = v; - }); - } [Test] public void TestSheared() @@ -59,7 +45,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { fillFlow = new FillFlowContainer { - Width = relativeWidth, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, @@ -94,7 +79,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2 { fillFlow = new FillFlowContainer { - Width = relativeWidth, Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, @@ -118,8 +102,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 }); } - [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised)); } diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs similarity index 98% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs index c93c41d558..d43026c960 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelect.cs @@ -22,7 +22,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneSongSelectV2 : ScreenTestScene + public partial class TestSceneSongSelect : ScreenTestScene { [Cached] private readonly ScreenFooter screenScreenFooter; @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2 [Cached] private readonly OsuLogo logo; - public TestSceneSongSelectV2() + public TestSceneSongSelect() { Children = new Drawable[] { diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs similarity index 92% rename from osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs rename to osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs index a72146352e..5173cb5673 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectV2Navigation.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneSongSelectNavigation.cs @@ -9,7 +9,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelectV2 { - public partial class TestSceneSongSelectV2Navigation : OsuGameTestScene + public partial class TestSceneSongSelectNavigation : OsuGameTestScene { public override void SetUpSteps() { From 054366b25dc83a3c7ed4598bb7b341b3ec7c568b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 13:07:42 +0900 Subject: [PATCH 627/670] Use zero baseline for legacy sprite text display --- osu.Game/Skinning/LegacySpriteText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs index fdd8716d5a..1028b5bb9d 100644 --- a/osu.Game/Skinning/LegacySpriteText.cs +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -96,7 +96,7 @@ namespace osu.Game.Skinning if (maxSize != null) texture = texture.WithMaximumSize(maxSize.Value); - glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 1f / texture.ScaleAdjust); + glyph = new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, 0, null), texture, 1f / texture.ScaleAdjust); } cache[character] = glyph; From ff1ab2bb0ef28335a834a1175a6dbc55b9e3f8b6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 14:59:40 +0900 Subject: [PATCH 628/670] Remove position-flipping logic from mania combo counters for now We need a general method to do this amicably, such as an HUD target that flips the position of its children when the direction is flipped. Something to consider later. --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 34 ------------------- .../Legacy/LegacyManiaComboCounter.cs | 34 ------------------- 2 files changed, 68 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 43d4e89cdb..04c08cc509 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -1,46 +1,12 @@ // 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.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Mania.Skinning.Argon { public partial class ArgonManiaComboCounter : ArgonComboCounter { - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - private IBindable direction = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - - direction = scrollingInfo.Direction.GetBoundCopy(); - direction.BindValueChanged(_ => updateAnchor()); - - // two schedules are required so that updateAnchor is executed in the next frame, - // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. - Schedule(() => Schedule(updateAnchor)); - } - - private void updateAnchor() - { - // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } - - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; - } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 5832210836..000e96540a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,9 +2,7 @@ // 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.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -24,38 +22,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; } - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - - private IBindable direction = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - - direction = scrollingInfo.Direction.GetBoundCopy(); - direction.BindValueChanged(_ => updateAnchor()); - - // two schedules are required so that updateAnchor is executed in the next frame, - // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. - Schedule(() => Schedule(updateAnchor)); - } - - private void updateAnchor() - { - // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction - if (!Anchor.HasFlag(Anchor.y1)) - { - Anchor &= ~(Anchor.y0 | Anchor.y2); - Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; - } - - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; - } - protected override void OnCountIncrement() { base.OnCountIncrement(); From 3a4546d62df3ceb50ddc7dac61e3989488b1a239 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 15:02:28 +0900 Subject: [PATCH 629/670] Remove `x` symbol from argon mania combo counter --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 2 +- osu.Game/Screens/Play/HUD/ArgonComboCounter.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 04c08cc509..2f93a1fb90 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { public partial class ArgonManiaComboCounter : ArgonComboCounter { - + protected override bool DisplayXSymbol => false; } } diff --git a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs index 3f74a8d4e8..e82e8f4b6f 100644 --- a/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ArgonComboCounter.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Play.HUD protected override double RollingDuration => 250; + protected virtual bool DisplayXSymbol => true; + [SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")] public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f) { @@ -76,15 +78,15 @@ namespace osu.Game.Screens.Play.HUD private int getDigitsRequiredForDisplayCount() { - // one for the single presumed starting digit, one for the "x" at the end. - int digitsRequired = 2; + // one for the single presumed starting digit, one for the "x" at the end (unless disabled). + int digitsRequired = DisplayXSymbol ? 2 : 1; long c = DisplayedCount; while ((c /= 10) > 0) digitsRequired++; return digitsRequired; } - protected override LocalisableString FormatCount(int count) => $@"{count}x"; + protected override LocalisableString FormatCount(int count) => DisplayXSymbol ? $@"{count}x" : count.ToString(); protected override IHasText CreateText() => Text = new ArgonCounterTextComponent(Anchor.TopLeft, MatchesStrings.MatchScoreStatsCombo.ToUpper()) { From 66adddbfb8020c019bbdf672ade3f00b4aead7ef Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 15:48:17 +0900 Subject: [PATCH 630/670] Actually bring back position-flipping logic and disable "closest" anchor --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 40 +++++++++++++++++++ .../Legacy/LegacyManiaComboCounter.cs | 34 ++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 2f93a1fb90..8d51b59324 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs @@ -1,6 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Play.HUD; namespace osu.Game.Rulesets.Mania.Skinning.Argon @@ -8,5 +12,41 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public partial class ArgonManiaComboCounter : ArgonComboCounter { protected override bool DisplayXSymbol => false; + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // the logic of flipping the position of the combo counter w.r.t. the direction does not work with "Closest" anchor, + // because it always forces the anchor to be top or bottom based on scrolling direction. + UsesFixedAnchor = true; + + direction = scrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in ArgonManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs index 000e96540a..5832210836 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyManiaComboCounter.cs @@ -2,7 +2,9 @@ // 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.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -22,6 +24,38 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy PopOutCountText.Colour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboBreakColour)?.Value ?? Color4.Red; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + + private IBindable direction = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + direction = scrollingInfo.Direction.GetBoundCopy(); + direction.BindValueChanged(_ => updateAnchor()); + + // two schedules are required so that updateAnchor is executed in the next frame, + // which is when the combo counter receives its Y position by the default layout in LegacyManiaSkinTransformer. + Schedule(() => Schedule(updateAnchor)); + } + + private void updateAnchor() + { + // if the anchor isn't a vertical center, set top or bottom anchor based on scroll direction + if (!Anchor.HasFlag(Anchor.y1)) + { + Anchor &= ~(Anchor.y0 | Anchor.y2); + Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; + } + + // since we flip the vertical anchor when changing scroll direction, + // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. + if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) + Y = -Y; + } + protected override void OnCountIncrement() { base.OnCountIncrement(); From 26da2c06372ca53e1772a59db6d7422946a4ee39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 16:16:48 +0900 Subject: [PATCH 631/670] Update `MultiplayerClient` test output with new knowledge --- .../Online/Multiplayer/MultiplayerClient.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d2c69c2ceb..07e779c2ba 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -5,14 +5,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Graphics; using osu.Game.Database; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.Countdown; @@ -22,7 +23,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using osu.Game.Localisation; namespace osu.Game.Online.Multiplayer { @@ -777,26 +777,22 @@ namespace osu.Game.Online.Multiplayer Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); + APIRoom.Playlist.RemoveAt(existingIndex); APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); } catch (Exception ex) { // Temporary code to attempt to figure out long-term failing tests. - bool success = true; - int indexOf = -1234; + StringBuilder exceptionText = new StringBuilder(); - try - { - indexOf = APIRoom!.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); - Room.Playlist[indexOf] = item; - } - catch - { - success = false; - } + exceptionText.AppendLine("MultiplayerClient test failure investigation"); + exceptionText.AppendLine($"Exception : {exceptionText}"); + exceptionText.AppendLine($"Lookup : {item.ID}"); + exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); + exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); - throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex); + throw new AggregateException(exceptionText.ToString()); } ItemChanged?.Invoke(item); From 4b279ecaa8b1209a65f3a44667aac9cbb9dfdb93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 16:44:15 +0900 Subject: [PATCH 632/670] Fix mistake --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 07e779c2ba..4aa0d92098 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -787,7 +787,7 @@ namespace osu.Game.Online.Multiplayer StringBuilder exceptionText = new StringBuilder(); exceptionText.AppendLine("MultiplayerClient test failure investigation"); - exceptionText.AppendLine($"Exception : {exceptionText}"); + exceptionText.AppendLine($"Exception : {ex.ToString()}"); exceptionText.AppendLine($"Lookup : {item.ID}"); exceptionText.AppendLine($"Items in Room.Playlist : {string.Join(',', Room.Playlist.Select(i => i.ID))}"); exceptionText.AppendLine($"Items in APIRoom.Playlist: {string.Join(',', APIRoom!.Playlist.Select(i => i.ID))}"); From 49c71f78631879a30177ef59797e379d2d9ad251 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:16:52 +0900 Subject: [PATCH 633/670] Fix beatmap skin always overriding ruleset HUD components --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 4 ++++ .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 44fc3ecc07..394fc5080d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -43,6 +43,10 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + // Our own ruleset components default. // todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead. return new DefaultSkinComponentsContainer(container => diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 9a8eaa7d7d..a0265dd6ee 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -53,6 +53,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + // Our own ruleset components default. switch (containerLookup.Target) { From a421231aad9894cb29d07646f465443ac607496c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:55:03 +0900 Subject: [PATCH 634/670] Fix beatmap skin on mania breaking HUD apart --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 6ac6f6ed18..1e06eb4817 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -92,6 +92,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; + // we don't have enough assets to display these components (this is especially the case on a "beatmap" skin). + if (!IsProvidingLegacyResources) + return null; + return new DefaultSkinComponentsContainer(container => { var combo = container.ChildrenOfType().FirstOrDefault(); From 358572ebb32b83c72129d88aa00046b4f82c60bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 16:57:29 +0900 Subject: [PATCH 635/670] Update code order to match everything else --- .../Argon/ManiaArgonSkinTransformer.cs | 35 ++++++++++--------- .../Legacy/ManiaLegacySkinTransformer.cs | 33 +++++++++-------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index 224db77f59..dbd690f890 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -29,9 +29,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Only handle per ruleset defaults here. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -40,21 +37,27 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d) return d; - return new DefaultSkinComponentsContainer(container => + switch (containerLookup.Target) { - var combo = container.ChildrenOfType().FirstOrDefault(); + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); - if (combo != null) - { - combo.ShowLabel.Value = false; - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = 200; - } - }) - { - new ArgonManiaComboCounter(), - }; + if (combo != null) + { + combo.ShowLabel.Value = false; + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = 200; + } + }) + { + new ArgonManiaComboCounter(), + }; + } + + return null; case GameplaySkinComponentLookup resultComponent: // This should eventually be moved to a skin setting, when supported. diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index 1e06eb4817..c25b77610a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -81,9 +81,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy switch (lookup) { case SkinComponentsContainerLookup containerLookup: - if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents) - return base.GetDrawableComponent(lookup); - // Modifications for global components. if (containerLookup.Ruleset == null) return base.GetDrawableComponent(lookup); @@ -96,20 +93,26 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy if (!IsProvidingLegacyResources) return null; - return new DefaultSkinComponentsContainer(container => + switch (containerLookup.Target) { - var combo = container.ChildrenOfType().FirstOrDefault(); + case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: + return new DefaultSkinComponentsContainer(container => + { + var combo = container.ChildrenOfType().FirstOrDefault(); - if (combo != null) - { - combo.Anchor = Anchor.TopCentre; - combo.Origin = Anchor.Centre; - combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; - } - }) - { - new LegacyManiaComboCounter(), - }; + if (combo != null) + { + combo.Anchor = Anchor.TopCentre; + combo.Origin = Anchor.Centre; + combo.Y = this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ComboPosition)?.Value ?? 0; + } + }) + { + new LegacyManiaComboCounter(), + }; + } + + return null; case GameplaySkinComponentLookup resultComponent: return getResult(resultComponent.Component); From 74272378735ddd9f88b0df0f6c53ed7edeb38170 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Aug 2024 17:03:08 +0900 Subject: [PATCH 636/670] Try make code look better --- .../Skinning/Argon/ArgonManiaComboCounter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs index 8d51b59324..5b23cea496 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonManiaComboCounter.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -43,10 +44,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon Anchor |= direction.Value == ScrollingDirection.Up ? Anchor.y2 : Anchor.y0; } - // since we flip the vertical anchor when changing scroll direction, - // we can use the sign of the Y value as an indicator to make the combo counter displayed correctly. - if ((Y < 0 && direction.Value == ScrollingDirection.Down) || (Y > 0 && direction.Value == ScrollingDirection.Up)) - Y = -Y; + // change the sign of the Y coordinate in line with the scrolling direction. + // i.e. if the user changes direction from down to up, the anchor is changed from top to bottom, and the Y is flipped from positive to negative here. + Y = Math.Abs(Y) * (direction.Value == ScrollingDirection.Up ? -1 : 1); } } } From b5f615882f1645d8a2cdd43cd2c8eb606068fde5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Aug 2024 17:25:30 +0900 Subject: [PATCH 637/670] Ensure the "Change Difficulty" menu uses up-to-date difficulty names Closes https://github.com/ppy/osu/issues/29391. --- osu.Game/Screens/Edit/Editor.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 71d4693ac6..167ac92874 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1286,10 +1286,23 @@ namespace osu.Game.Screens.Edit foreach (var beatmap in rulesetBeatmaps) { bool isCurrentDifficulty = playableBeatmap.BeatmapInfo.Equals(beatmap); - difficultyItems.Add(new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty)); + var difficultyMenuItem = new DifficultyMenuItem(beatmap, isCurrentDifficulty, SwitchToDifficulty); + difficultyItems.Add(difficultyMenuItem); } } + // Ensure difficulty names are updated when modified in the editor. + // Maybe we could trigger less often but this seems to work well enough. + editorBeatmap.SaveStateTriggered += () => + { + foreach (var beatmapInfo in Beatmap.Value.BeatmapSetInfo.Beatmaps) + { + var menuItem = difficultyItems.OfType().FirstOrDefault(i => i.BeatmapInfo.Equals(beatmapInfo)); + if (menuItem != null) + menuItem.Text.Value = string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? "(unnamed)" : beatmapInfo.DifficultyName; + } + }; + return new EditorMenuItem(EditorStrings.ChangeDifficulty) { Items = difficultyItems }; } From f717938a288974cef9f4fb4c94c18024158c7549 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 22:49:05 +0200 Subject: [PATCH 638/670] Fix grid snap slider placement double-click does not make new segment if anchor not hovered --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 91cd270af6..013f790f65 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -359,8 +359,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } // Update the cursor position. - var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); - cursor.Position = ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; + cursor.Position = getCursorPosition(); } else if (cursor != null) { @@ -374,6 +373,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } + private Vector2 getCursorPosition() + { + var result = positionSnapProvider?.FindSnappedPositionAndTime(inputManager.CurrentState.Mouse.Position, state == SliderPlacementState.ControlPoints ? SnapType.GlobalGrids : SnapType.All); + return ToLocalSpace(result?.ScreenSpacePosition ?? inputManager.CurrentState.Mouse.Position) - HitObject.Position; + } + /// /// Whether a new control point can be placed at the current mouse position. /// @@ -386,7 +391,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var lastPiece = controlPointVisualiser.Pieces.Single(p => p.ControlPoint == last); lastPoint = last; - return lastPiece.IsHovered != true; + // We may only place a new control point if the cursor is not overlapping with the last control point. + // If snapping is enabled, the cursor may not hover the last piece while still placing the control point at the same position. + return !lastPiece.IsHovered && (last is null || Vector2.DistanceSquared(last.Position, getCursorPosition()) > 1f); } private void placeNewControlPoint() From 29fda745a42a12b9e53f7e6b417c9219e0701d58 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 22:59:26 +0200 Subject: [PATCH 639/670] add failure test --- .../Editor/TestSceneSliderPlacementBlueprint.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs index bc1e4f9864..aa6a6f08d8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderPlacementBlueprint.cs @@ -299,6 +299,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }); assertControlPointTypeDuringPlacement(0, PathType.BSpline(4)); + AddStep("press alt-2", () => + { + InputManager.PressKey(Key.AltLeft); + InputManager.Key(Key.Number2); + InputManager.ReleaseKey(Key.AltLeft); + }); + assertControlPointTypeDuringPlacement(0, PathType.BEZIER); + AddStep("start new segment via S", () => InputManager.Key(Key.S)); assertControlPointTypeDuringPlacement(2, PathType.LINEAR); @@ -309,7 +317,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor addClickStep(MouseButton.Right); assertPlaced(true); - assertFinalControlPointType(0, PathType.BSpline(4)); + assertFinalControlPointType(0, PathType.BEZIER); assertFinalControlPointType(2, PathType.PERFECT_CURVE); } From 00e210147a457849a799296493e965efb029ad38 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 15 Aug 2024 23:11:07 +0200 Subject: [PATCH 640/670] Fix inputs being eaten by PathControlPointVisualizer when no control points are selected --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6251d17d85..df369dcef5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -309,8 +309,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (!e.AltPressed) return false; + // If no pieces are selected, we can't change the path type. + if (Pieces.All(p => !p.IsSelected.Value)) + return false; + var type = path_types[e.Key - Key.Number1]; + // The first control point can never be inherit type if (Pieces[0].IsSelected.Value && type == null) return false; From fda17a5a72f327139cf982ebb68fbb25add6b5b4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 13 Aug 2024 23:52:52 -0700 Subject: [PATCH 641/670] Expose `BeatmapCardNormal` height const --- .../Beatmaps/Drawables/Cards/BeatmapCardNormal.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index c6ba4f234a..46ab7ec5f6 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override Drawable IdleContent => idleBottomContent; protected override Drawable DownloadInProgressContent => downloadProgressBar; - private const float height = 100; + public const float HEIGHT = 100; [Cached] private readonly BeatmapCardContent content; @@ -42,14 +42,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards public BeatmapCardNormal(APIBeatmapSet beatmapSet, bool allowExpansion = true) : base(beatmapSet, allowExpansion) { - content = new BeatmapCardContent(height); + content = new BeatmapCardContent(HEIGHT); } [BackgroundDependencyLoader] private void load() { Width = WIDTH; - Height = height; + Height = HEIGHT; FillFlowContainer leftIconArea = null!; FillFlowContainer titleBadgeArea = null!; @@ -65,7 +65,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards thumbnail = new BeatmapCardThumbnail(BeatmapSet, BeatmapSet) { Name = @"Left (icon) area", - Size = new Vector2(height), + Size = new Vector2(HEIGHT), Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { @@ -77,8 +77,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { - X = height - CORNER_RADIUS, - Width = WIDTH - height + CORNER_RADIUS, + X = HEIGHT - CORNER_RADIUS, + Width = WIDTH - HEIGHT + CORNER_RADIUS, FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = CORNER_RADIUS, ButtonsExpandedWidth = 30, From e2bf02cf948edc82d7cbe1049b0fe9638fc656bc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 10 Aug 2024 00:49:51 -0700 Subject: [PATCH 642/670] Fix preview play button having incorrect click area --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 5 ++--- osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 976f797760..1f6f638618 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -90,10 +90,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void Update() { base.Update(); - progress.Progress = playButton.Progress.Value; - playButton.Scale = new Vector2(DrawWidth / 100); - progress.Size = new Vector2(50 * DrawWidth / 100); + progress.Progress = playButton.Progress.Value; + progress.Size = new Vector2(50 * playButton.DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS)); } private void updateState() diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f808fd21b7..f6caf4815d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -79,6 +79,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { base.Update(); + icon.Scale = new Vector2(DrawWidth / (BeatmapCardNormal.HEIGHT - BeatmapCard.CORNER_RADIUS)); + if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded) progress.Value = previewTrack.CurrentTime / previewTrack.Length; else From 68bad9a277b33b9ae35c2f4ea1fd7d3a128832b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Aug 2024 17:39:45 +0900 Subject: [PATCH 643/670] Attempt file operations more than once in another test instance See https://github.com/ppy/osu/pull/29433/checks?check_run_id=28833985792. --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 2b23581984..db9ecd90b9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -276,8 +276,11 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("delete beatmap files", () => { - foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) - Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath())); + FileUtils.AttemptOperation(() => + { + foreach (var file in Game.Beatmap.Value.BeatmapSetInfo.Files.Where(f => Path.GetExtension(f.Filename) == ".osu")) + Game.Storage.Delete(Path.Combine("files", file.File.GetStoragePath())); + }); }); AddStep("invalidate cache", () => From 5624c1d304a8cf40428d88e4e36b5262a1274604 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 16 Aug 2024 13:22:09 +0200 Subject: [PATCH 644/670] Make break periods in bottom timeline transparent --- .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 17e0d47676..3cff976f72 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -70,7 +70,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativeSizeAxes = Axes.Both; InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray6; + Colour = colours.Gray7; + Alpha = 0.8f; } public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; From d1d195cf18f872b8a57f696d58c12aae1ed31fcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2024 02:30:59 +0900 Subject: [PATCH 645/670] Fix incorrect skin lookup shortcutting causing sprites to no longer work --- osu.Game/Skinning/ArgonSkin.cs | 8 ++++---- osu.Game/Skinning/LegacySkin.cs | 8 ++++---- osu.Game/Skinning/TrianglesSkin.cs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 85abb1edcd..c66df82e0d 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -94,13 +94,13 @@ namespace osu.Game.Skinning // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.SongSelect: @@ -257,7 +257,7 @@ namespace osu.Game.Skinning return null; } - return null; + return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8f6e634dd6..bbca0178d5 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,12 +356,12 @@ namespace osu.Game.Skinning public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup) { - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + switch (containerLookup.Target) { case SkinComponentsContainerLookup.TargetArea.MainHUDComponents: @@ -445,7 +445,7 @@ namespace osu.Game.Skinning return null; } - return null; + return base.GetDrawableComponent(lookup); } private Texture? getParticleTexture(HitResult result) diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs index 29abb1949f..7971aee794 100644 --- a/osu.Game/Skinning/TrianglesSkin.cs +++ b/osu.Game/Skinning/TrianglesSkin.cs @@ -64,12 +64,12 @@ namespace osu.Game.Skinning // Temporary until default skin has a valid hit lighting. if ((lookup as SkinnableSprite.SpriteComponentLookup)?.LookupName == @"lighting") return Drawable.Empty(); - if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) - return c; - switch (lookup) { case SkinComponentsContainerLookup containerLookup: + if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer c) + return c; + // Only handle global level defaults for now. if (containerLookup.Ruleset != null) return null; @@ -178,7 +178,7 @@ namespace osu.Game.Skinning return null; } - return null; + return base.GetDrawableComponent(lookup); } public override IBindable? GetConfig(TLookup lookup) From f74263db8111d5abe4825816f938697b00bab562 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Aug 2024 00:59:44 +0300 Subject: [PATCH 646/670] Remove extra box in OnlinePlayBackgroundScreen --- .../Components/OnlinePlayBackgroundScreen.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index ea422f83e3..ef7c1747e9 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -3,10 +3,8 @@ using System.Threading; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Rooms; @@ -20,16 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Components private CancellationTokenSource? cancellationSource; private PlaylistItemBackground? background; - protected OnlinePlayBackgroundScreen() - { - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MinValue, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f)) - }); - } - [BackgroundDependencyLoader] private void load() { @@ -83,6 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Components } newBackground.Depth = newDepth; + newBackground.Colour = ColourInfo.GradientVertical(new Color4(0.1f, 0.1f, 0.1f, 1f), new Color4(0.4f, 0.4f, 0.4f, 1f)); newBackground.BlurTo(new Vector2(10)); AddInternal(background = newBackground); From 04a2d67ca4131d56d22a6cf3d6ca2c432726f01b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 17 Aug 2024 15:13:44 +0900 Subject: [PATCH 647/670] Fix legacy combo counter bounce animation not always playing As mentioned [in discord](https://discord.com/channels/188630481301012481/1097318920991559880/1274231995261649006). --- osu.Game/Skinning/LegacyDefaultComboCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyDefaultComboCounter.cs b/osu.Game/Skinning/LegacyDefaultComboCounter.cs index f633358993..6c81b1f959 100644 --- a/osu.Game/Skinning/LegacyDefaultComboCounter.cs +++ b/osu.Game/Skinning/LegacyDefaultComboCounter.cs @@ -41,9 +41,6 @@ namespace osu.Game.Skinning protected override void OnCountIncrement() { - scheduledPopOut?.Cancel(); - scheduledPopOut = null; - DisplayedCountText.Show(); PopOutCountText.Text = FormatCount(Current.Value); From 3cd5820b5b903227d80b24ae57faa8996467ceed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 17 Aug 2024 10:34:39 +0300 Subject: [PATCH 648/670] Make PositionSnapGrid a BufferedContainer --- .../Compose/Components/PositionSnapGrid.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index e576ac1e49..cbdf02488a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -2,15 +2,17 @@ // 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.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract partial class PositionSnapGrid : CompositeDrawable + public abstract partial class PositionSnapGrid : BufferedContainer { /// /// The position of the origin of this in local coordinates. @@ -20,7 +22,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); protected PositionSnapGrid() + : base(cachedFrameBuffer: true) { + BackgroundColour = Color4.White.Opacity(0); + StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); @@ -30,7 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { base.Update(); - if (GridCache.IsValid) return; + if (GridCache.IsValid) + return; ClearInternal(); @@ -38,6 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components CreateContent(); GridCache.Validate(); + ForceRedraw(); } protected abstract void CreateContent(); @@ -53,7 +60,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.X, Height = lineWidth, Y = 0, @@ -62,28 +68,26 @@ namespace osu.Game.Screens.Edit.Compose.Components { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, + Height = lineWidth }, new Box { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, + Width = lineWidth }, new Box { Colour = Colour4.White, Alpha = 0.3f, - Origin = Anchor.TopCentre, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, + Width = lineWidth }, }); } From 6dd08e9a964a978d715c6dd7fe148e7392f8aa73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 17 Aug 2024 11:26:46 -0700 Subject: [PATCH 649/670] Fix beatmap carousel panels not blocking hover of other panels in song select --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 4c9ac57d9d..755008d370 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -166,6 +166,8 @@ namespace osu.Game.Screens.Select.Carousel return true; } + protected override bool OnHover(HoverEvent e) => true; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From e75ae4a37bc0c427e73e975a48a7cb92b067db0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 04:04:24 +0900 Subject: [PATCH 650/670] More hardening of `TestMultiplayerClient` to attempt to fix test failures --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 4c3deac1d7..efa9dc4990 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -208,6 +208,9 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override async Task JoinRoom(long roomId, string? password = null) { + if (RoomJoined || ServerAPIRoom != null) + throw new InvalidOperationException("Already joined a room"); + roomId = clone(roomId); password = clone(password); @@ -260,6 +263,8 @@ namespace osu.Game.Tests.Visual.Multiplayer protected override Task LeaveRoomInternal() { RoomJoined = false; + ServerAPIRoom = null; + ServerRoom = null; return Task.CompletedTask; } From 95d06333c1d948d6d8372bd8b708fc2b38a6817c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 13:49:59 +0900 Subject: [PATCH 651/670] Fix typo in editor field --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3c1d0fbb1c..484fbd5084 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); [Cached] - protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); + protected readonly FreehandSliderToolboxGroup FreehandSliderToolboxGroup = new FreehandSliderToolboxGroup(); [BackgroundDependencyLoader] private void load() @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Edit RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, - FreehandlSliderToolboxGroup + FreehandSliderToolboxGroup } ); } From 4a3f4c3a55ac513b85d1d1434b4ac145e3e53e78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 14:46:36 +0900 Subject: [PATCH 652/670] Don't duck music when effect volume is set to zero Addresses https://github.com/ppy/osu/discussions/28984. --- osu.Game/Overlays/MusicController.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index d9bb92b4b7..27c7cd0f49 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -60,6 +60,8 @@ namespace osu.Game.Overlays [Resolved] private RealmAccess realm { get; set; } = null!; + private BindableNumber sampleVolume = null!; + private readonly BindableDouble audioDuckVolume = new BindableDouble(1); private AudioFilter audioDuckFilter = null!; @@ -69,6 +71,7 @@ namespace osu.Game.Overlays { AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer)); audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume); + sampleVolume = audio.VolumeSample.GetBoundCopy(); } protected override void LoadComplete() @@ -269,6 +272,10 @@ namespace osu.Game.Overlays /// A which will restore the duck operation when disposed. public IDisposable Duck(DuckParameters? parameters = null) { + // Don't duck if samples have no volume, it sounds weird. + if (sampleVolume.Value == 0) + return new InvokeOnDisposal(() => { }); + parameters ??= new DuckParameters(); duckOperations.Add(parameters); @@ -302,6 +309,10 @@ namespace osu.Game.Overlays /// Parameters defining the ducking operation. public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null) { + // Don't duck if samples have no volume, it sounds weird. + if (sampleVolume.Value == 0) + return; + parameters ??= new DuckParameters(); IDisposable duckOperation = Duck(parameters); From ca92c116b5acc75945278fffc46d0d49d651ca9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 15:01:11 +0900 Subject: [PATCH 653/670] Fix osu!catch trail spacing not matching osu!stable expectations Closes https://github.com/ppy/osu/issues/28997. --- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 21faec56de..338e1364a9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Catch.UI if (Catcher.Dashing || Catcher.HyperDashing) { - double generationInterval = Catcher.HyperDashing ? 25 : 50; + const double trail_generation_interval = 16; - if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval) + if (Time.Current - catcherTrails.LastDashTrailTime >= trail_generation_interval) displayCatcherTrail(Catcher.HyperDashing ? CatcherTrailAnimation.HyperDashing : CatcherTrailAnimation.Dashing); } From 1bd2f4c6a2a2c77411d2bf74ec3e4a408f82ed59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 15:45:18 +0900 Subject: [PATCH 654/670] Fix skin editor components sidebar not reloading when changing skins Closes https://github.com/ppy/osu/issues/29098. --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 484af34603..03acf1e68c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -421,6 +421,9 @@ namespace osu.Game.Overlays.SkinEditor if (targetContainer != null) changeHandler = new SkinEditorChangeHandler(targetContainer); hasBegunMutating = true; + + // Reload sidebar components. + selectedTarget.TriggerChange(); } /// From 005b1038a3e31092cdf8174bc42ddfe6f497ef25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 20:23:25 +0900 Subject: [PATCH 655/670] Change "hold for menu" button to only show for touch by default --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/Localisation/GameplaySettingsStrings.cs | 5 +++++ .../Settings/Sections/Gameplay/HUDSettings.cs | 5 +++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 16 ++++++++++++++-- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d00856dd80..8d6c244b35 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -205,6 +205,8 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.EditorTimelineShowTimingChanges, true); SetDefault(OsuSetting.EditorTimelineShowTicks, true); + + SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false); } protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup) @@ -429,5 +431,6 @@ namespace osu.Game.Configuration HideCountryFlags, EditorTimelineShowTimingChanges, EditorTimelineShowTicks, + AlwaysShowHoldForMenuButton } } diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 8ee76fdd55..6de61f7ebe 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -84,6 +84,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AlwaysShowGameplayLeaderboard => new TranslatableString(getKey(@"gameplay_leaderboard"), @"Always show gameplay leaderboard"); + /// + /// "Always show hold for menu button" + /// + public static LocalisableString AlwaysShowHoldForMenuButton => new TranslatableString(getKey(@"always_show_hold_for_menu_button"), @"Always show hold for menu button"); + /// /// "Always play first combo break sound" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs index 3e67b2f103..f4dd319152 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/HUDSettings.cs @@ -41,6 +41,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Current = config.GetBindable(OsuSetting.GameplayLeaderboard), }, new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.AlwaysShowHoldForMenuButton, + Current = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton), + }, + new SettingsCheckbox { ClassicDefault = false, LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail, diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 6d045e5f01..41600c2bb8 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -40,6 +40,10 @@ namespace osu.Game.Screens.Play.HUD private OsuSpriteText text; + private Bindable alwaysShow; + + public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; + public HoldForMenuButton() { Direction = FillDirection.Horizontal; @@ -50,7 +54,7 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader(true)] - private void load(Player player) + private void load(Player player, OsuConfigManager config) { Children = new Drawable[] { @@ -71,6 +75,8 @@ namespace osu.Game.Screens.Play.HUD }; AutoSizeAxes = Axes.Both; + + alwaysShow = config.GetBindable(OsuSetting.AlwaysShowHoldForMenuButton); } [Resolved] @@ -119,7 +125,9 @@ namespace osu.Game.Screens.Play.HUD if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) Alpha = 1; - else + else if (touchActive.Value) + Alpha = 0.08f; + else if (alwaysShow.Value) { float minAlpha = touchActive.Value ? .08f : 0; @@ -127,6 +135,10 @@ namespace osu.Game.Screens.Play.HUD Math.Clamp(Clock.ElapsedFrameTime, 0, 200), Alpha, Math.Clamp(1 - positionalAdjust, minAlpha, 1), 0, 200, Easing.OutQuint); } + else + { + Alpha = 0; + } } private partial class HoldButton : HoldToConfirmContainer, IKeyBindingHandler From 6985e2e657c4ed875aa8305f4a5d8f7fab651d1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Aug 2024 20:28:02 +0900 Subject: [PATCH 656/670] Increase default visibility on touch platforms --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 41600c2bb8..89d083eca9 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Play.HUD { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; + public readonly Bindable IsPaused = new Bindable(); public readonly Bindable ReplayLoaded = new Bindable(); @@ -42,8 +44,6 @@ namespace osu.Game.Screens.Play.HUD private Bindable alwaysShow; - public override bool PropagatePositionalInputSubTree => alwaysShow.Value || touchActive.Value; - public HoldForMenuButton() { Direction = FillDirection.Horizontal; @@ -123,10 +123,13 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); + // While the button is hovered or still animating, keep fully visible. if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) Alpha = 1; + // When touch input is detected, keep visible at a constant opacity. else if (touchActive.Value) - Alpha = 0.08f; + Alpha = 0.5f; + // Otherwise, if the user chooses, show it when the mouse is nearby. else if (alwaysShow.Value) { float minAlpha = touchActive.Value ? .08f : 0; @@ -136,9 +139,7 @@ namespace osu.Game.Screens.Play.HUD Alpha, Math.Clamp(1 - positionalAdjust, minAlpha, 1), 0, 200, Easing.OutQuint); } else - { Alpha = 0; - } } private partial class HoldButton : HoldToConfirmContainer, IKeyBindingHandler From 67de43213c4a097dcf211d42549fd86b4f89133f Mon Sep 17 00:00:00 2001 From: TheOmyNomy Date: Mon, 19 Aug 2024 23:21:06 +1000 Subject: [PATCH 657/670] Apply current cursor expansion scale to trail parts --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 15 +++++++++++---- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 5 +++++ .../UI/Cursor/OsuCursorContainer.cs | 13 ++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 6452444fed..a4bccb0aff 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -38,6 +38,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private double timeOffset; private float time; + /// + /// The scale used on creation of a new trail part. + /// + public Vector2 NewPartScale = Vector2.One; + private Anchor trailOrigin = Anchor.Centre; protected Anchor TrailOrigin @@ -188,6 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { parts[currentIndex].Position = localSpacePosition; parts[currentIndex].Time = time + 1; + parts[currentIndex].Scale = NewPartScale; ++parts[currentIndex].InvalidationID; currentIndex = (currentIndex + 1) % max_sprites; @@ -199,6 +205,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { public Vector2 Position; public float Time; + public Vector2 Scale; public long InvalidationID; } @@ -280,7 +287,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), TexturePosition = textureRect.BottomLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, @@ -289,7 +296,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y)), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), TexturePosition = textureRect.BottomRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, @@ -298,7 +305,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X), part.Position.Y - texture.DisplayHeight * originPosition.Y), + Position = new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), TexturePosition = textureRect.TopRight, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, @@ -307,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor vertexBatch.Add(new TexturedTrailVertex { - Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X, part.Position.Y - texture.DisplayHeight * originPosition.Y), + Position = new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y), TexturePosition = textureRect.TopLeft, TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index d8f50c1f5d..0bb316e0aa 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private SkinnableCursor skinnableCursor => (SkinnableCursor)cursorSprite.Drawable; + /// + /// The current expanded scale of the cursor. + /// + public Vector2 CurrentExpandedScale => skinnableCursor.ExpandTarget?.Scale ?? Vector2.One; + public IBindable CursorScale => cursorScale; private readonly Bindable cursorScale = new BindableFloat(1); diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index ba8a634ff7..9ac81d13a7 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -23,14 +23,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public new OsuCursor ActiveCursor => (OsuCursor)base.ActiveCursor; protected override Drawable CreateCursor() => new OsuCursor(); - protected override Container Content => fadeContainer; private readonly Container fadeContainer; private readonly Bindable showTrail = new Bindable(true); - private readonly Drawable cursorTrail; + private readonly SkinnableDrawable cursorTrail; private readonly CursorRippleVisualiser rippleVisualiser; @@ -39,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor InternalChild = fadeContainer = new Container { RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new CompositeDrawable[] { cursorTrail = new SkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorTrail), _ => new DefaultCursorTrail(), confineMode: ConfineMode.NoScaling), rippleVisualiser = new CursorRippleVisualiser(), @@ -79,6 +78,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor ActiveCursor.Contract(); } + protected override void Update() + { + base.Update(); + + // We can direct cast here because the cursor trail is always a derived class of CursorTrail. + ((CursorTrail)cursorTrail.Drawable).NewPartScale = ActiveCursor.CurrentExpandedScale; + } + public bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) From 5ba1b4fe3d16bd95204137857e20cf343f5e701a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 01:12:57 +0900 Subject: [PATCH 658/670] Update test coverage --- .../Visual/Gameplay/TestSceneHoldForMenuButton.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 3c225d60e0..cd1334165b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -21,11 +21,19 @@ namespace osu.Game.Tests.Visual.Gameplay protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms - private HoldForMenuButton holdForMenuButton; + private HoldForMenuButton holdForMenuButton = null!; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; [SetUpSteps] public void SetUpSteps() { + AddStep("set button always on", () => + { + config.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true); + }); + AddStep("create button", () => { exitAction = false; From 86c3c115f6fbe315a4ef99c9218b73239e703573 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 12:15:33 +0900 Subject: [PATCH 659/670] Make grid/distance snap binds T/Y respectively --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index bbcf4fa2d4..4476160f81 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -54,11 +54,8 @@ namespace osu.Game.Rulesets.Osu.Edit protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons() - .Concat(DistanceSnapProvider.CreateTernaryButtons()) - .Concat(new[] - { - new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }) - }); + .Append(new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap })) + .Concat(DistanceSnapProvider.CreateTernaryButtons()); private BindableList selectedHitObjects; From 180c4a02485398dd6af523c4665476aa51a1665e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 14:20:52 +0900 Subject: [PATCH 660/670] Fix tests by removing assumption --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 9ac81d13a7..8c0871d54f 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -82,8 +82,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { base.Update(); - // We can direct cast here because the cursor trail is always a derived class of CursorTrail. - ((CursorTrail)cursorTrail.Drawable).NewPartScale = ActiveCursor.CurrentExpandedScale; + if (cursorTrail.Drawable is CursorTrail trail) + trail.NewPartScale = ActiveCursor.CurrentExpandedScale; } public bool OnPressed(KeyBindingPressEvent e) From 4a19ed7472f27859ef47dc2907c617c33b786365 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 15:20:48 +0900 Subject: [PATCH 661/670] Add test --- .../TestSceneCursorTrail.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index 4db66fde4b..17f365f820 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -88,6 +88,21 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("trail is disjoint", () => this.ChildrenOfType().Single().DisjointTrail, () => Is.True); } + [Test] + public void TestClickExpand() + { + createTest(() => new Container + { + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(10), + Child = new CursorTrail(), + }); + + AddStep("expand", () => this.ChildrenOfType().Single().NewPartScale = new Vector2(3)); + AddWaitStep("let the cursor trail draw a bit", 5); + AddStep("contract", () => this.ChildrenOfType().Single().NewPartScale = Vector2.One); + } + private void createTest(Func createContent) => AddStep("create trail", () => { Clear(); From 2e67ff1d92fa25d4faf231b1d26403926ae92773 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 16:14:05 +0900 Subject: [PATCH 662/670] Fix tests --- .../Editor/TestSceneOsuEditorGrids.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index b17f4e7487..b70ecfbba8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -24,24 +24,24 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestGridToggles() { - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); gridActive(false); - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); gridActive(true); - AddStep("disable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("disable distance snap grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); gridActive(true); - AddStep("disable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("disable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any()); gridActive(false); } @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { double distanceSnap = double.PositiveInfinity; - AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("enable distance snap grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestGridSizeToggling() { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddStep("enable rectangular grid", () => InputManager.Key(Key.T)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); gridSizeIs(4); From 373ff47a94ac29fed06f5c49dd6d5ff438e8fe74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 09:53:40 +0200 Subject: [PATCH 663/670] Remove dead row attribute classes These aren't shown on the control point table since difficulty and sample control points were moved into objects. --- .../Screens/Edit/Timing/ControlPointTable.cs | 6 -- .../RowAttributes/DifficultyRowAttribute.cs | 44 -------------- .../RowAttributes/SampleRowAttribute.cs | 57 ------------------- 3 files changed, 107 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs delete mode 100644 osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 2204fabf57..8dc0ced30e 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -323,14 +323,8 @@ namespace osu.Game.Screens.Edit.Timing case TimingControlPoint timing: return new TimingRowAttribute(timing); - case DifficultyControlPoint difficulty: - return new DifficultyRowAttribute(difficulty); - case EffectControlPoint effect: return new EffectRowAttribute(effect); - - case SampleControlPoint sample: - return new SampleRowAttribute(sample); } throw new ArgumentOutOfRangeException(nameof(controlPoint), $"Control point type {controlPoint.GetType()} is not supported"); diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs deleted file mode 100644 index 43f3739503..0000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/DifficultyRowAttribute.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public partial class DifficultyRowAttribute : RowAttribute - { - private readonly BindableNumber speedMultiplier; - - private OsuSpriteText text = null!; - - public DifficultyRowAttribute(DifficultyControlPoint difficulty) - : base(difficulty, "difficulty") - { - speedMultiplier = difficulty.SliderVelocityBindable.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - Content.AddRange(new Drawable[] - { - new AttributeProgressBar(Point) - { - Current = speedMultiplier, - }, - text = new AttributeText(Point) - { - Width = 45, - }, - }); - - speedMultiplier.BindValueChanged(_ => updateText(), true); - } - - private void updateText() => text.Text = $"{speedMultiplier.Value:n2}x"; - } -} diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs deleted file mode 100644 index e86a991521..0000000000 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/SampleRowAttribute.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Edit.Timing.RowAttributes -{ - public partial class SampleRowAttribute : RowAttribute - { - private AttributeText sampleText = null!; - private OsuSpriteText volumeText = null!; - - private readonly Bindable sampleBank; - private readonly BindableNumber volume; - - public SampleRowAttribute(SampleControlPoint sample) - : base(sample, "sample") - { - sampleBank = sample.SampleBankBindable.GetBoundCopy(); - volume = sample.SampleVolumeBindable.GetBoundCopy(); - } - - [BackgroundDependencyLoader] - private void load() - { - AttributeProgressBar progress; - - Content.AddRange(new Drawable[] - { - sampleText = new AttributeText(Point), - progress = new AttributeProgressBar(Point), - volumeText = new AttributeText(Point) - { - Width = 40, - }, - }); - - volume.BindValueChanged(vol => - { - progress.Current.Value = vol.NewValue / 100f; - updateText(); - }, true); - - sampleBank.BindValueChanged(_ => updateText(), true); - } - - private void updateText() - { - volumeText.Text = $"{volume.Value}%"; - sampleText.Text = $"{sampleBank.Value}"; - } - } -} From c85b04bca5854e3f6cab6bf79aca17de1a2d1d77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:11:22 +0900 Subject: [PATCH 664/670] Add more test coverage to better show overlapping break / kiai sections --- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index ddca2f8553..677d3135ba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -24,7 +24,10 @@ namespace osu.Game.Tests.Visual.Editing beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = 100 }); beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 }); + beatmap.ControlPointInfo.Add(80000, new EffectControlPoint { KiaiMode = true }); + beatmap.ControlPointInfo.Add(110000, new EffectControlPoint { KiaiMode = false }); beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 }; + beatmap.Breaks.Add(new ManualBreakPeriod(90000, 120000)); editorBeatmap = new EditorBeatmap(beatmap); } From bccc797bcb0ac6598af5ac4145d71cb9b84664cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:45:37 +0900 Subject: [PATCH 665/670] Move break display to background of summary timeline --- .../Components/Timelines/Summary/Parts/BreakPart.cs | 6 +++--- .../Components/Timelines/Summary/SummaryTimeline.cs | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 3cff976f72..be3a7b7268 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -69,9 +69,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Both; - InternalChild = new Circle { RelativeSizeAxes = Axes.Both }; - Colour = colours.Gray7; - Alpha = 0.8f; + InternalChild = new Box { RelativeSizeAxes = Axes.Both }; + Colour = colours.Gray5; + Alpha = 0.4f; } public LocalisableString TooltipText => $"{breakPeriod.StartTime.ToEditorFormattedString()} - {breakPeriod.EndTime.ToEditorFormattedString()} break time"; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index a495442c1d..4ab7c88178 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -59,6 +59,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary RelativeSizeAxes = Axes.Both, Height = 0.4f, }, + new BreakPart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, new ControlPointPart { Anchor = Anchor.Centre, @@ -73,13 +79,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary RelativeSizeAxes = Axes.Both, Height = 0.4f }, - new BreakPart - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Height = 0.15f - }, new MarkerPart { RelativeSizeAxes = Axes.Both }, }; } From 73f2f5cb1268f39ca91a729050ba248c8c62689e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Aug 2024 17:59:55 +0900 Subject: [PATCH 666/670] Fix more tests --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 2 ++ osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 16b2a54a45..91f22a291c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay holdForMenu.Action += () => activated = true; }); + AddStep("set hold button always visible", () => localConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); @@ -214,6 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay progress.ChildrenOfType().Single().OnSeek += _ => seeked = true; }); + AddStep("set hold button always visible", () => localConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 030f2592ed..6aa2c4e40d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -320,6 +320,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitViaHoldToExit() { + AddStep("set hold button always visible", () => LocalConfig.SetValue(OsuSetting.AlwaysShowHoldForMenuButton, true)); + AddStep("exit", () => { InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer)); From bb964e32fa5a1e5f2aeb1b3f14308f9c85be02ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 13:36:52 +0200 Subject: [PATCH 667/670] Fix crash on attempting to edit particular beatmaps Closes https://github.com/ppy/osu/issues/29492. I'm not immediately sure why this happened, but some old locally modified beatmaps in my local realm database have a `BeatDivisor` of 0 stored, which is then passed to `BindableBeatDivisor.SetArbitraryDivisor()`, which then blows up. To stop this from happening, just refuse to use values outside of a sane range. --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 10 +++++++++- .../Edit/Compose/Components/BeatDivisorControl.cs | 6 +++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 4b0726658f..3bb1b4e079 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -16,6 +16,9 @@ namespace osu.Game.Screens.Edit { public static readonly int[] PREDEFINED_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 }; + public const int MINIMUM_DIVISOR = 1; + public const int MAXIMUM_DIVISOR = 64; + public Bindable ValidDivisors { get; } = new Bindable(BeatDivisorPresetCollection.COMMON); public BindableBeatDivisor(int value = 1) @@ -30,8 +33,12 @@ namespace osu.Game.Screens.Edit /// /// The intended divisor. /// Forces changing the valid divisors to a known preset. - public void SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) + /// Whether the divisor was successfully set. + public bool SetArbitraryDivisor(int divisor, bool preferKnownPresets = false) { + if (divisor < MINIMUM_DIVISOR || divisor > MAXIMUM_DIVISOR) + return false; + // If the current valid divisor range doesn't contain the proposed value, attempt to find one which does. if (preferKnownPresets || !ValidDivisors.Value.Presets.Contains(divisor)) { @@ -44,6 +51,7 @@ namespace osu.Game.Screens.Edit } Value = divisor; + return true; } private void updateBindableProperties() diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 1d8266d610..3c2a66b8bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -330,14 +330,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private void setPresetsFromTextBoxEntry() { - if (!int.TryParse(divisorTextBox.Text, out int divisor) || divisor < 1 || divisor > 64) + if (!int.TryParse(divisorTextBox.Text, out int divisor) || !BeatDivisor.SetArbitraryDivisor(divisor)) { + // the text either didn't parse as a divisor, or the divisor was not set due to being out of range. + // force a state update to reset the text box's value to the last sane value. updateState(); return; } - BeatDivisor.SetArbitraryDivisor(divisor); - this.HidePopover(); } From c2dd2ad9783412d61a819805a42f1fa4a9dfd12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Aug 2024 13:40:57 +0200 Subject: [PATCH 668/670] Clamp beat divisor to sane range when decoding In my view this is a nice change, but do note that on its own it does nothing to fix https://github.com/ppy/osu/issues/29492, because of `BeatmapInfo` reference management foibles when opening the editor. See also: https://github.com/ppy/osu/issues/20883#issuecomment-1288149271, https://github.com/ppy/osu/pull/28473. --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 9418a389aa..b068c87fbb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Screens.Edit; namespace osu.Game.Beatmaps.Formats { @@ -336,7 +337,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); + beatmap.BeatmapInfo.BeatDivisor = Math.Clamp(Parsing.ParseInt(pair.Value), BindableBeatDivisor.MINIMUM_DIVISOR, BindableBeatDivisor.MAXIMUM_DIVISOR); break; case @"GridSize": From 2011d5525f7aab8fa1809d16f8801dffaa507f51 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 20 Aug 2024 22:21:10 +0900 Subject: [PATCH 669/670] Add flaky test attribute to some tests See occurences like https://github.com/ppy/osu/actions/runs/10471058714. --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5a71369976..5af7540f6f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(2000, 0)] [TestCase(3000, first_hit_object - 3000)] [TestCase(10000, first_hit_object - 10000)] + [FlakyTest] public void TestLeadInProducesCorrectStartTime(double leadIn, double expectedStartTime) { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) @@ -41,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(0, 0)] [TestCase(-1000, -1000)] [TestCase(-10000, -10000)] + [FlakyTest] public void TestStoryboardProducesCorrectStartTimeSimpleAlpha(double firstStoryboardEvent, double expectedStartTime) { var storyboard = new Storyboard(); @@ -64,6 +66,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestCase(0, 0, true)] [TestCase(-1000, -1000, true)] [TestCase(-10000, -10000, true)] + [FlakyTest] public void TestStoryboardProducesCorrectStartTimeFadeInAfterOtherEvents(double firstStoryboardEvent, double expectedStartTime, bool addEventToLoop) { const double loop_start_time = -20000; From 20658ef4eeebbf3d09515c777305ed145a9646b3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 21 Aug 2024 00:02:05 +0900 Subject: [PATCH 670/670] Fix legacy key counter position not matching stable --- .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 6 ++---- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index 81279456d5..f3626eb55d 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -56,10 +56,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy { // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + keyCounter.Origin = Anchor.TopRight; + keyCounter.Position = new Vector2(0, -40) * 1.6f; } }) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 491eb02e26..457c191583 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -69,10 +69,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { // set the anchor to top right so that it won't squash to the return button to the top keyCounter.Anchor = Anchor.CentreRight; - keyCounter.Origin = Anchor.CentreRight; - keyCounter.X = 0; - // 340px is the default height inherit from stable - keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y; + keyCounter.Origin = Anchor.TopRight; + keyCounter.Position = new Vector2(0, -40) * 1.6f; } var combo = container.OfType().FirstOrDefault();