From 00574a528868d1ef31907484e71623969c0cfeaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:28 +0900 Subject: [PATCH 001/120] Use ISample everywhere in Skin GetSample lookup path --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 2 +- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs | 2 +- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 4 ++-- .../NonVisual/Skinning/LegacySkinAnimationTest.cs | 2 +- osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs | 2 +- .../Visual/Gameplay/TestSceneSkinnableDrawable.cs | 6 +++--- osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/ISkin.cs | 2 +- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/LegacySkinTransformer.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 +- osu.Game/Skinning/SkinProvidingContainer.cs | 4 ++-- 18 files changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index cbbbacfe19..24ccae895d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy return animation == null ? null : new LegacyManiaJudgementPiece(result, animation); } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { // layered hit sounds never play in mania if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs index e2d9f144c0..8fd13c7417 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests return null; } - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 8dbb48c048..19b6779619 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Tests public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - public Sample GetSample(ISampleInfo sampleInfo) => null; + public ISample GetSample(ISampleInfo sampleInfo) => null; public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; public IBindable GetConfig(TLookup lookup) => null; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 9f29675230..d1214d3456 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } - public override Sample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); + public override ISample GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs index 3ded3009bd..883791c35c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 7a0dd5b719..6fa1839556 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Gameplay public void TestRetrieveTopLevelSample() { ISkin skin = null; - Sample channel = null; + ISample channel = null; AddStep("create skin", () => skin = new TestSkin("test-sample", this)); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample"))); @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Gameplay public void TestRetrieveSampleInSubFolder() { ISkin skin = null; - Sample channel = null; + ISample channel = null; AddStep("create skin", () => skin = new TestSkin("folder/test-sample", this)); AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample"))); diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs index da004b9088..b08a228de3 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinAnimationTest.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.NonVisual.Skinning } public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotSupportedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotSupportedException(); public IBindable GetConfig(TLookup lookup) => throw new NotSupportedException(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs index 414f7d3f88..732a3f3f42 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs @@ -219,7 +219,7 @@ namespace osu.Game.Tests.Skins public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 44142b69d7..7a6e2f54c2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -298,7 +298,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } @@ -309,7 +309,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); } @@ -321,7 +321,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); - public Sample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index d688e9cb21..d792405eeb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay public Drawable GetDrawableComponent(ISkinComponent component) => source?.GetDrawableComponent(component); public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => source?.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => source?.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => source?.GetConfig(lookup); public void TriggerSourceChanged() diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 346c7b3c65..0b3f5f3cde 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null; - public override Sample GetSample(ISampleInfo sampleInfo) => null; + public override ISample GetSample(ISampleInfo sampleInfo) => null; public override IBindable GetConfig(TLookup lookup) { diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs index ef8de01042..73f7cf6d39 100644 --- a/osu.Game/Skinning/ISkin.cs +++ b/osu.Game/Skinning/ISkin.cs @@ -48,7 +48,7 @@ namespace osu.Game.Skinning /// The requested sample. /// A matching sample channel, or null if unavailable. [CanBeNull] - Sample GetSample(ISampleInfo sampleInfo); + ISample GetSample(ISampleInfo sampleInfo); /// /// Retrieve a configuration value. diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index fb4207b647..3ec205e897 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -39,7 +39,7 @@ namespace osu.Game.Skinning return base.GetConfig(lookup); } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy && legacy.CustomSampleBank == 0) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index e5d0217671..1ee797098c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -452,7 +452,7 @@ namespace osu.Game.Skinning return null; } - public override Sample GetSample(ISampleInfo sampleInfo) + public override ISample GetSample(ISampleInfo sampleInfo) { IEnumerable lookupNames; diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index e2f4a82a54..ae8faf1a3b 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => Source.GetTexture(componentName, wrapModeS, wrapModeT); - public virtual Sample GetSample(ISampleInfo sampleInfo) + public virtual ISample GetSample(ISampleInfo sampleInfo) { if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) return Source.GetSample(sampleInfo); diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index e8d84b49f9..13f5385c20 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -19,7 +19,7 @@ namespace osu.Game.Skinning public abstract Drawable GetDrawableComponent(ISkinComponent componentName); - public abstract Sample GetSample(ISampleInfo sampleInfo); + public abstract ISample GetSample(ISampleInfo sampleInfo); public Texture GetTexture(string componentName) => GetTexture(componentName, default, default); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2826c826a5..9e730b2ce1 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT); - public Sample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); + public ISample GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo); public IBindable GetConfig(TLookup lookup) => CurrentSkin.Value.GetConfig(lookup); diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index ba67d0a678..cf22b2e820 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -59,9 +59,9 @@ namespace osu.Game.Skinning return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT); } - public Sample GetSample(ISampleInfo sampleInfo) + public ISample GetSample(ISampleInfo sampleInfo) { - Sample sourceChannel; + ISample sourceChannel; if (AllowSampleLookup(sampleInfo) && (sourceChannel = skin?.GetSample(sampleInfo)) != null) return sourceChannel; From 4aff54412a0a23707b4284510705dd2b2c91492d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:39 +0900 Subject: [PATCH 002/120] Move dispose method to end of file --- osu.Game/Skinning/LegacySkin.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1ee797098c..12abc4d867 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -100,13 +100,6 @@ namespace osu.Game.Skinning true) != null); } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - Textures?.Dispose(); - Samples?.Dispose(); - } - public override IBindable GetConfig(TLookup lookup) { switch (lookup) @@ -504,5 +497,12 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Textures?.Dispose(); + Samples?.Dispose(); + } } } From 880fe820733d9159c6eb866f85336b01081ea1c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:32:51 +0900 Subject: [PATCH 003/120] Add sample wrapper in LegacySkin to keep a reference and avoid GC death --- osu.Game/Skinning/LegacySkin.cs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 12abc4d867..5d015ca5ab 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -461,12 +461,43 @@ namespace osu.Game.Skinning var sample = Samples?.Get(lookup); if (sample != null) - return sample; + return new LegacySkinSample(sample, this); } return null; } + /// + /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. + /// + private class LegacySkinSample : ISample + { + private readonly Sample sample; + + [UsedImplicitly] + private readonly LegacySkin skin; + + public LegacySkinSample(Sample sample, LegacySkin skin) + { + this.sample = sample; + this.skin = skin; + } + + public SampleChannel Play() + { + return sample.Play(); + } + + public SampleChannel GetChannel() + { + return sample.GetChannel(); + } + + public double Length => sample.Length; + + public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; + } + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) { var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); From 487a39eea95a5215d5aec323c923a600144ba51a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Feb 2021 18:52:34 +0900 Subject: [PATCH 004/120] Update interface implementations with framework changes --- .../TestSceneDrawableRulesetDependencies.cs | 6 ++-- .../UI/DrawableRulesetDependencies.cs | 11 +++++++ osu.Game/Skinning/LegacySkin.cs | 33 +++++++++++++++++++ osu.Game/Skinning/PoolableSkinnableSample.cs | 8 +++++ osu.Game/Skinning/SkinnableSound.cs | 17 ++++++---- 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs index 787f72ba79..a2f2c5e41f 100644 --- a/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs +++ b/osu.Game.Tests/Rulesets/TestSceneDrawableRulesetDependencies.cs @@ -118,9 +118,11 @@ namespace osu.Game.Tests.Rulesets public BindableNumber Frequency => throw new NotImplementedException(); public BindableNumber Tempo => throw new NotImplementedException(); - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotImplementedException(); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index deec948d14..6c31f05337 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -114,6 +114,11 @@ namespace osu.Game.Rulesets.UI public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + throw new NotImplementedException(); + } + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotSupportedException(); public BindableNumber Volume => throw new NotSupportedException(); @@ -124,6 +129,12 @@ namespace osu.Game.Rulesets.UI public BindableNumber Tempo => throw new NotSupportedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => throw new NotImplementedException(); + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); public IBindable AggregateVolume => throw new NotSupportedException(); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5d015ca5ab..2edc36a770 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -496,6 +497,38 @@ namespace osu.Game.Skinning public double Length => sample.Length; public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; + public BindableNumber Volume => sample.Volume; + + public BindableNumber Balance => sample.Balance; + + public BindableNumber Frequency => sample.Frequency; + + public BindableNumber Tempo => sample.Tempo; + + public void BindAdjustments(IAggregateAudioAdjustment component) + { + sample.BindAdjustments(component); + } + + public void UnbindAdjustments(IAggregateAudioAdjustment component) + { + sample.UnbindAdjustments(component); + } + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + sample.AddAdjustment(type, adjustBindable); + } + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + sample.RemoveAdjustment(type, adjustBindable); + } + + public void RemoveAllAdjustments(AdjustableProperty type) + { + sample.RemoveAllAdjustments(type); + } } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9025fdbd0f..b12fbf90f3 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -165,6 +165,14 @@ namespace osu.Game.Skinning public BindableNumber Tempo => sampleContainer.Tempo; + public void BindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.BindAdjustments(component); + + public void UnbindAdjustments(IAggregateAudioAdjustment component) => sampleContainer.UnbindAdjustments(component); + + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => sampleContainer.RemoveAdjustment(type, adjustBindable); diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index b3db2d6558..c971517c7f 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -176,14 +176,19 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.AddAdjustment(type, adjustBindable); + public void BindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.BindAdjustments(component); - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.RemoveAdjustment(type, adjustBindable); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => samplesContainer.UnbindAdjustments(component); - public void RemoveAllAdjustments(AdjustableProperty type) - => samplesContainer.RemoveAllAdjustments(type); + public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.AddAdjustment(type, adjustBindable); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => samplesContainer.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) => samplesContainer.RemoveAllAdjustments(type); /// /// Whether any samples are currently playing. From 0bda9e4b794f16d108f234563746202bf3b8a160 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 18:31:33 +0900 Subject: [PATCH 005/120] Implement some new methods --- osu.Game/Skinning/LegacySkin.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 2edc36a770..6bdc4575c9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -529,6 +529,14 @@ namespace osu.Game.Skinning { sample.RemoveAllAdjustments(type); } + + public IBindable AggregateVolume => sample.AggregateVolume; + + public IBindable AggregateBalance => sample.AggregateBalance; + + public IBindable AggregateFrequency => sample.AggregateFrequency; + + public IBindable AggregateTempo => sample.AggregateTempo; } private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) From f48e017ac901bcd5387a87d53b2a5ab5dc771d06 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Feb 2021 18:34:05 +0900 Subject: [PATCH 006/120] Move nested class to bottom of file --- osu.Game/Skinning/LegacySkin.cs | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 6bdc4575c9..571d65e28b 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -468,7 +468,45 @@ namespace osu.Game.Skinning return null; } - /// + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + { + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); + } + + foreach (var l in lookupNames) + yield return l; + + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + yield return hitSample.Name; + } + + private IEnumerable getFallbackNames(string componentName) + { + // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. + yield return componentName; + + // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). + string lastPiece = componentName.Split('/').Last(); + yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Textures?.Dispose(); + Samples?.Dispose(); + } + + /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// private class LegacySkinSample : ISample @@ -538,43 +576,5 @@ namespace osu.Game.Skinning public IBindable AggregateTempo => sample.AggregateTempo; } - - private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) - { - var lookupNames = hitSample.LookupNames.SelectMany(getFallbackNames); - - if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) - { - // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. - // using .EndsWith() is intentional as it ensures parity in all edge cases - // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). - lookupNames = lookupNames.Where(name => !name.EndsWith(hitSample.Suffix, StringComparison.Ordinal)); - } - - foreach (var l in lookupNames) - yield return l; - - // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. - // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, - // which is why this is done locally here. - yield return hitSample.Name; - } - - private IEnumerable getFallbackNames(string componentName) - { - // May be something like "Gameplay/osu/approachcircle" from lazer, or "Arrows/note1" from a user skin. - yield return componentName; - - // Fall back to using the last piece for components coming from lazer (e.g. "Gameplay/osu/approachcircle" -> "approachcircle"). - string lastPiece = componentName.Split('/').Last(); - yield return componentName.StartsWith("Gameplay/taiko/", StringComparison.Ordinal) ? "taiko-" + lastPiece : lastPiece; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - Textures?.Dispose(); - Samples?.Dispose(); - } } } From b419d2c2e27762af7701c1823ae9e7786a730e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 18 Mar 2021 19:52:38 +0100 Subject: [PATCH 007/120] Fix invalid xmldoc indent --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 571d65e28b..b69e99773c 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -506,7 +506,7 @@ namespace osu.Game.Skinning Samples?.Dispose(); } - /// + /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// private class LegacySkinSample : ISample From 5f31304d05fa71b356651643608aa34f8207e323 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 14:00:26 +0900 Subject: [PATCH 008/120] Give each type of slider path type a unique colour to help visually distinguish them --- .../Components/PathControlPointPiece.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e9838de63d..311ab8ee62 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; @@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; - Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow; + Color4 colour = getColourFromNodeType(); if (IsHovered || IsSelected.Value) colour = colour.Lighten(1); @@ -203,5 +204,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components marker.Colour = colour; marker.Scale = new Vector2(slider.Scale); } + + private Color4 getColourFromNodeType() + { + if (!(ControlPoint.Type.Value is PathType pathType)) + return colours.Yellow; + + switch (pathType) + { + case PathType.Catmull: + return colours.Seafoam; + + case PathType.Bezier: + return colours.Pink; + + case PathType.PerfectCurve: + return colours.PurpleDark; + + default: + return colours.Red; + } + } } } From 0e821e857e34f810e1ede0c9f28df6678602caf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:23:31 +0900 Subject: [PATCH 009/120] Remove unnecessary duplicated skin changed handling For some reason we were handling this both in `DrawableSkinnableSound` and `PoolableSkinnableSample` in very similar ways. Only one seems required. --- osu.Game/Skinning/SkinnableSound.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index edd3a2cdd3..e447f9c44c 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -139,12 +139,6 @@ namespace osu.Game.Skinning samplesContainer.ForEach(c => c.Stop()); } - protected override void SkinChanged(ISkinSource skin, bool allowFallback) - { - base.SkinChanged(skin, allowFallback); - updateSamples(); - } - private void updateSamples() { bool wasPlaying = IsPlaying; From bf4317d3f06ecd3ba29c2f43c33561e5557c3b24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:34:19 +0900 Subject: [PATCH 010/120] Ensure looping is disabled on old samples when switching skins --- osu.Game/Skinning/PoolableSkinnableSample.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 9103a6a960..09e087d0f2 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -83,6 +83,14 @@ namespace osu.Game.Skinning bool wasPlaying = Playing; + if (activeChannel != null) + { + // when switching away from previous samples, we don't want to call Stop() on them as it sounds better to let them play out. + // this may change in the future if we use PoolableSkinSample in more locations than gameplay. + // we *do* want to turn off looping, else we end up with an infinite looping sample running in the background. + activeChannel.Looping = false; + } + sampleContainer.Clear(); Sample = null; From 9491e6394a65de287bb05c96188db69ca7837158 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 15:46:43 +0900 Subject: [PATCH 011/120] Include the bundled skins when selecting a random skin --- osu.Game/Skinning/SkinManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 601b77e782..752c742a45 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -86,7 +86,7 @@ namespace osu.Game.Skinning public void SelectRandomSkin() { // choose from only user skins, removing the current selection to ensure a new one is chosen. - var randomChoices = GetAllUsableSkins().Where(s => s.ID > 0 && s.ID != CurrentSkinInfo.Value.ID).ToArray(); + var randomChoices = GetAllUsableSkins().Where(s => s.ID != CurrentSkinInfo.Value.ID).ToArray(); if (randomChoices.Length == 0) { From a9c4fa442a983534897b6d91e3fe9dd60d97dead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 16:47:39 +0900 Subject: [PATCH 012/120] Avoid potential crash if an overlay is toggled before it has been loaded --- osu.Game/OsuGame.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 66b9141ce8..e5e1f6946e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -756,6 +756,10 @@ namespace osu.Game private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { + // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. + if (overlay.LoadState < LoadState.Ready) + return; + otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); // show above others if not visible at all, else leave at current depth. From 27c38db14dee71d4f5a40300acbe90eb84d8bf3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 16:58:08 +0900 Subject: [PATCH 013/120] Add tooltips for slider path nodes which aren't inheriting --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 311ab8ee62..1390675a1a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; 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.Input.Events; using osu.Game.Graphics; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// A visualisation of a single in a . /// - public class PathControlPointPiece : BlueprintPiece + public class PathControlPointPiece : BlueprintPiece, IHasTooltip { public Action RequestSelection; @@ -225,5 +226,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return colours.Red; } } + + public string TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty; } } From 0195d654cacb2e496ce7cc2b3c3b3991111eabb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 17:09:49 +0900 Subject: [PATCH 014/120] Increase the precision of speed multiplier to match osu-stable --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 0bc5605051..73337ab6f5 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { - Precision = 0.1, + Precision = 0.01, Default = 1, MinValue = 0.1, MaxValue = 10 From 32c571fc94a0ca6521b25226670f79238dc9bf98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 17:13:30 +0900 Subject: [PATCH 015/120] Adjust keyboard step to be something sensible --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 3 ++- osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index b87b8961f8..9d80ca0b14 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -18,7 +18,8 @@ namespace osu.Game.Screens.Edit.Timing { multiplierSlider = new SliderWithTextBoxInput("Speed Multiplier") { - Current = new DifficultyControlPoint().SpeedMultiplierBindable + Current = new DifficultyControlPoint().SpeedMultiplierBindable, + KeyboardStep = 0.1f } }); } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index f2f9f76143..10a5771520 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -69,6 +69,15 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + /// + /// A custom step value for each key press which actuates a change on this control. + /// + public float KeyboardStep + { + get => slider.KeyboardStep; + set => slider.KeyboardStep = value; + } + public Bindable Current { get => slider.Current; From 1c865682ae3b0d4116fa8483b1e173c67ab079a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 16:25:50 +0900 Subject: [PATCH 016/120] Add tablet configuration tests --- .../Settings/TestSceneTabletAreaSelection.cs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs new file mode 100644 index 0000000000..30e265baaa --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs @@ -0,0 +1,104 @@ +// 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 OpenTabletDriver.Plugin.Tablet; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneTabletAreaSelection : OsuTestScene + { + private TabletAreaSelection areaSelection; + + [BackgroundDependencyLoader] + private void load() + { + DigitizerIdentifier testTablet = new DigitizerIdentifier + { + // size specifications in millimetres. + Width = 160, + Height = 100, + }; + + AddRange(new[] + { + areaSelection = new TabletAreaSelection(testTablet) + { + State = { Value = Visibility.Visible } + } + }); + } + } + + public class TabletAreaSelection : OsuFocusedOverlayContainer + { + private readonly DigitizerIdentifier tablet; + + private readonly Container tabletContainer; + private readonly Container usableAreaContainer; + + public TabletAreaSelection(DigitizerIdentifier tablet) + { + RelativeSizeAxes = Axes.Both; + + this.tablet = tablet; + + InternalChildren = new Drawable[] + { + tabletContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(3), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + usableAreaContainer = new Container + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Yellow, + }, + new OsuSpriteText + { + Text = "usable area", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Font = OsuFont.Default.With(size: 12) + } + } + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + // TODO: handle tablet device changes etc. + tabletContainer.Size = new Vector2(tablet.Width, tablet.Height); + + usableAreaContainer.Position = new Vector2(10, 30); + usableAreaContainer.Size = new Vector2(80, 60); + } + } +} From d026c8da851a5fbd399206b8efff28b76c09c643 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Mar 2021 18:37:46 +0900 Subject: [PATCH 017/120] Initial pass of configuration interface --- .../Settings/TestSceneTabletSettings.cs | 40 +++++++ .../Sections/Input/TabletAreaSelection.cs | 79 ++++++------- .../Settings/Sections/Input/TabletSettings.cs | 111 ++++++++++++++++++ .../Settings/Sections/InputSection.cs | 6 + 4 files changed, 194 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs rename osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs => osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs (55%) create mode 100644 osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs new file mode 100644 index 0000000000..be5b355e06 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Platform; +using osu.Game.Overlays.Settings.Sections.Input; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneTabletSettings : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(GameHost host) + { + var tabletHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); + + if (tabletHandler == null) + return; + + tabletHandler.AreaOffset.MinValue = new Size(0, 0); + tabletHandler.AreaOffset.MaxValue = new Size(160, 100); + tabletHandler.AreaOffset.Value = new Size(10, 10); + + tabletHandler.AreaSize.MinValue = new Size(0, 0); + tabletHandler.AreaSize.MaxValue = new Size(160, 100); + tabletHandler.AreaSize.Value = new Size(100, 80); + + AddRange(new Drawable[] + { + new TabletSettings(tabletHandler), + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs similarity index 55% rename from osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs rename to osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 30e265baaa..31a2768735 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -1,65 +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 NUnit.Framework; -using OpenTabletDriver.Plugin.Tablet; +using System.Drawing; 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.Handlers.Tablet; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; + using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Settings +namespace osu.Game.Overlays.Settings.Sections.Input { - [TestFixture] - public class TestSceneTabletAreaSelection : OsuTestScene + public class TabletAreaSelection : CompositeDrawable { - private TabletAreaSelection areaSelection; - - [BackgroundDependencyLoader] - private void load() - { - DigitizerIdentifier testTablet = new DigitizerIdentifier - { - // size specifications in millimetres. - Width = 160, - Height = 100, - }; - - AddRange(new[] - { - areaSelection = new TabletAreaSelection(testTablet) - { - State = { Value = Visibility.Visible } - } - }); - } - } - - public class TabletAreaSelection : OsuFocusedOverlayContainer - { - private readonly DigitizerIdentifier tablet; + private readonly ITabletHandler handler; private readonly Container tabletContainer; private readonly Container usableAreaContainer; - public TabletAreaSelection(DigitizerIdentifier tablet) - { - RelativeSizeAxes = Axes.Both; + private readonly Bindable areaOffset = new BindableSize(); + private readonly Bindable areaSize = new BindableSize(); + private readonly Bindable tabletSize = new BindableSize(); - this.tablet = tablet; + public TabletAreaSelection(ITabletHandler handler) + { + this.handler = handler; + + Padding = new MarginPadding(5); InternalChildren = new Drawable[] { tabletContainer = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(3), + Masking = true, + CornerRadius = 5, + BorderThickness = 2, + BorderColour = Color4.Black, Children = new Drawable[] { new Box @@ -69,6 +50,8 @@ namespace osu.Game.Tests.Visual.Settings }, usableAreaContainer = new Container { + Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box @@ -94,11 +77,23 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load() { - // TODO: handle tablet device changes etc. - tabletContainer.Size = new Vector2(tablet.Width, tablet.Height); + areaOffset.BindTo(handler.AreaOffset); + areaOffset.BindValueChanged(val => + { + usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + }, true); - usableAreaContainer.Position = new Vector2(10, 30); - usableAreaContainer.Size = new Vector2(80, 60); + areaSize.BindTo(handler.AreaSize); + areaSize.BindValueChanged(val => + { + usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + }, true); + + ((IBindable)tabletSize).BindTo(handler.TabletSize); + tabletSize.BindValueChanged(val => + { + tabletContainer.ResizeTo(new Vector2(tabletSize.Value.Width, tabletSize.Value.Height), 100, Easing.OutQuint); + }, true); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs new file mode 100644 index 0000000000..5df9c879eb --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -0,0 +1,111 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input.Handlers.Tablet; +using osu.Game.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Input +{ + public class TabletSettings : SettingsSubsection + { + private readonly ITabletHandler tabletHandler; + + private readonly BindableSize areaOffset = new BindableSize(); + private readonly BindableSize areaSize = new BindableSize(); + private readonly BindableSize tabletSize = new BindableSize(); + + private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; + + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + + protected override string Header => "Tablet"; + + public TabletSettings(ITabletHandler tabletHandler) + { + this.tabletHandler = tabletHandler; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) + { + // TODO: this should all eventually be replaced with a control that handles BindableSize. + areaOffset.BindTo(tabletHandler.AreaOffset); + areaOffset.BindValueChanged(val => + { + offsetX.Value = val.NewValue.Width; + offsetY.Value = val.NewValue.Height; + }, true); + + offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); + offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + + areaSize.BindTo(tabletHandler.AreaSize); + areaSize.BindValueChanged(val => + { + sizeX.Value = val.NewValue.Width; + sizeY.Value = val.NewValue.Height; + }, true); + + sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); + sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); + + ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); + tabletSize.BindValueChanged(val => + { + // todo: these should propagate from a TabletChanged event or similar. + offsetX.MaxValue = val.NewValue.Width; + sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + + offsetY.MaxValue = val.NewValue.Height; + sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + + updateDisplay(); + }, true); + } + + private void updateDisplay() + { + if (tabletSize.Value == System.Drawing.Size.Empty) + { + Clear(); + return; + } + + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "Offset X", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Offset Y", + Current = offsetY + }, + new SettingsSlider + { + LabelText = "Size X", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Size Y", + Current = sizeY + }, + new TabletAreaSelection(tabletHandler) + { + RelativeSizeAxes = Axes.X, + Height = 100, + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index 8d5944f5bf..6e99891794 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -8,6 +8,7 @@ using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Midi; using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Game.Overlays.Settings.Sections.Input; @@ -55,6 +56,11 @@ namespace osu.Game.Overlays.Settings.Sections switch (handler) { + // ReSharper disable once SuspiciousTypeConversion.Global (net standard fuckery) + case ITabletHandler th: + section = new TabletSettings(th); + break; + case MouseHandler mh: section = new MouseSettings(mh); break; From 3b7edf13337a26a9d358b2dd70eef5773ba73332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 14:45:21 +0900 Subject: [PATCH 018/120] Make tablet display always fit to size of settings area --- .../Settings/TestSceneTabletSettings.cs | 26 ++++- .../Sections/Input/TabletAreaSelection.cs | 105 ++++++++++-------- 2 files changed, 78 insertions(+), 53 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index be5b355e06..6455f51ab9 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Drawing; -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; @@ -18,16 +18,13 @@ namespace osu.Game.Tests.Visual.Settings [BackgroundDependencyLoader] private void load(GameHost host) { - var tabletHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); - - if (tabletHandler == null) - return; + var tabletHandler = new TestTabletHandler(); tabletHandler.AreaOffset.MinValue = new Size(0, 0); tabletHandler.AreaOffset.MaxValue = new Size(160, 100); tabletHandler.AreaOffset.Value = new Size(10, 10); - tabletHandler.AreaSize.MinValue = new Size(0, 0); + tabletHandler.AreaSize.MinValue = new Size(10, 10); tabletHandler.AreaSize.MaxValue = new Size(160, 100); tabletHandler.AreaSize.Value = new Size(100, 80); @@ -35,6 +32,23 @@ namespace osu.Game.Tests.Visual.Settings { new TabletSettings(tabletHandler), }); + + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); + AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); + AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); + AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); + } + + public class TestTabletHandler : ITabletHandler + { + private readonly Bindable tabletSize = new Bindable(); + + public BindableSize AreaOffset { get; } = new BindableSize(); + public BindableSize AreaSize { get; } = new BindableSize(); + public IBindable TabletSize => tabletSize; + public BindableBool Enabled { get; } = new BindableBool(true); + + public void SetTabletSize(Size size) => tabletSize.Value = size; } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 31a2768735..775aceb5f9 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.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.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,7 +12,6 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; - using osuTK.Graphics; namespace osu.Game.Overlays.Settings.Sections.Input @@ -20,8 +20,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { private readonly ITabletHandler handler; - private readonly Container tabletContainer; - private readonly Container usableAreaContainer; + private Container tabletContainer; + private Container usableAreaContainer; private readonly Bindable areaOffset = new BindableSize(); private readonly Bindable areaSize = new BindableSize(); @@ -30,53 +30,50 @@ namespace osu.Game.Overlays.Settings.Sections.Input public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; - - Padding = new MarginPadding(5); - - InternalChildren = new Drawable[] - { - tabletContainer = new Container - { - Masking = true, - CornerRadius = 5, - BorderThickness = 2, - BorderColour = Color4.Black, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - usableAreaContainer = new Container - { - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Yellow, - }, - new OsuSpriteText - { - Text = "usable area", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Font = OsuFont.Default.With(size: 12) - } - } - }, - } - } - }; } [BackgroundDependencyLoader] private void load() { + Padding = new MarginPadding(5); + + InternalChild = tabletContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 5, + BorderThickness = 2, + BorderColour = Color4.Black, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + usableAreaContainer = new Container + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Yellow, + }, + new OsuSpriteText + { + Text = "usable area", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Font = OsuFont.Default.With(size: 12) + } + } + }, + } + }; + areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { @@ -92,8 +89,22 @@ namespace osu.Game.Overlays.Settings.Sections.Input ((IBindable)tabletSize).BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { - tabletContainer.ResizeTo(new Vector2(tabletSize.Value.Width, tabletSize.Value.Height), 100, Easing.OutQuint); - }, true); + tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + }); + } + + protected override void Update() + { + base.Update(); + + var size = tabletSize.Value; + + float fitX = size.Width / DrawWidth; + float fitY = size.Height / DrawHeight; + + float adjust = MathF.Max(fitX, fitY); + + tabletContainer.Scale = new Vector2(1 / adjust); } } } From 926e40925ef0ba15bb6a3994f71e291dfd7196c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 15:57:06 +0900 Subject: [PATCH 019/120] Add exclude rule to fix dynamic compilations issues with settings sections --- osu.Game/Overlays/Settings/SettingsSubsection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 1b82d973e9..6abf6283b9 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -8,10 +8,12 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Testing; using osu.Game.Graphics; namespace osu.Game.Overlays.Settings { + [ExcludeFromDynamicCompile] public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren { protected override Container Content => FlowContent; From 0a6525baee8afa45b44432e8acc0e13fb0ca1be7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 15:57:29 +0900 Subject: [PATCH 020/120] Fix slider bars reloading each time the tablet size is changed --- .../Settings/Sections/Input/TabletSettings.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 5df9c879eb..ac4a42e984 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -4,10 +4,8 @@ using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; -using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -33,9 +31,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input } [BackgroundDependencyLoader] - private void load(OsuConfigManager osuConfig, FrameworkConfigManager config) + private void load() { - // TODO: this should all eventually be replaced with a control that handles BindableSize. areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => { @@ -59,6 +56,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { + if (tabletSize.Value == System.Drawing.Size.Empty) + return; + // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = val.NewValue.Width; sizeX.Default = sizeX.MaxValue = val.NewValue.Width; @@ -72,11 +72,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateDisplay() { - if (tabletSize.Value == System.Drawing.Size.Empty) - { - Clear(); + if (Children.Count > 0) return; - } Children = new Drawable[] { @@ -103,7 +100,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, - Height = 100, + Height = 300, } }; } From 94f184d113200bdca2c7c89496948a9e2e3d9990 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:02:39 +0900 Subject: [PATCH 021/120] Add feedback when area extends beyond tablet size --- .../Sections/Input/TabletAreaSelection.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 775aceb5f9..77b16a970d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Yellow, + Colour = Color4.White, }, new OsuSpriteText { @@ -78,21 +78,37 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindValueChanged(val => { usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + checkBounds(); }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + checkBounds(); }, true); ((IBindable)tabletSize).BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + checkBounds(); }); } + [Resolved] + private OsuColour colour { get; set; } + + private void checkBounds() + { + Size areaExtent = areaOffset.Value + areaSize.Value; + + bool isWithinBounds = areaExtent.Width < tabletSize.Value.Width + && areaExtent.Height < tabletSize.Value.Height; + + usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + } + protected override void Update() { base.Update(); From 464702182d6064023064720824403e8277bd19a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:23:46 +0900 Subject: [PATCH 022/120] Consume device name --- .../Visual/Settings/TestSceneTabletSettings.cs | 1 + .../Settings/Sections/Input/TabletAreaSelection.cs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 6455f51ab9..1c9cd6c2ba 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -46,6 +46,7 @@ namespace osu.Game.Tests.Visual.Settings public BindableSize AreaOffset { get; } = new BindableSize(); public BindableSize AreaSize { get; } = new BindableSize(); public IBindable TabletSize => tabletSize; + public string DeviceName => "test tablet T-421"; public BindableBool Enabled { get; } = new BindableBool(true); public void SetTabletSize(Size size) => tabletSize.Value = size; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 77b16a970d..54a14cd822 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -27,6 +27,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly Bindable areaSize = new BindableSize(); private readonly Bindable tabletSize = new BindableSize(); + private OsuSpriteText tabletName; + public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; @@ -44,13 +46,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input Masking = true, CornerRadius = 5, BorderThickness = 2, - BorderColour = Color4.Black, + BorderColour = colour.Gray3, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Colour = colour.Gray1, }, usableAreaContainer = new Container { @@ -59,7 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Alpha = 0.6f, }, new OsuSpriteText { @@ -71,6 +73,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input } } }, + tabletName = new OsuSpriteText + { + Padding = new MarginPadding(3), + Font = OsuFont.Default.With(size: 8) + }, } }; @@ -92,6 +99,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); + tabletName.Text = handler.DeviceName; checkBounds(); }); } From 382109c7a221f83b02b00a70e002551e7244157f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:08 +0900 Subject: [PATCH 023/120] Make test scene feel more like settings (width-wise) --- .../Visual/Settings/TestSceneTabletSettings.cs | 9 ++++++++- osu.Game/Overlays/SettingsPanel.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 1c9cd6c2ba..3d65db9420 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; +using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Tests.Visual.Settings @@ -30,7 +31,13 @@ namespace osu.Game.Tests.Visual.Settings AddRange(new Drawable[] { - new TabletSettings(tabletHandler), + new TabletSettings(tabletHandler) + { + RelativeSizeAxes = Axes.None, + Width = SettingsPanel.WIDTH, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } }); AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index f1270f750e..8f3274b2b5 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays private const float sidebar_width = Sidebar.DEFAULT_WIDTH; - protected const float WIDTH = 400; + public const float WIDTH = 400; protected Container ContentContainer; From 2dc2cb04c317de94a762dbdb74dfede0a91b33ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:20 +0900 Subject: [PATCH 024/120] Fix bounds check becoming false when using full area --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 54a14cd822..6a3cc46e2b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -111,8 +111,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Size areaExtent = areaOffset.Value + areaSize.Value; - bool isWithinBounds = areaExtent.Width < tabletSize.Value.Width - && areaExtent.Height < tabletSize.Value.Height; + bool isWithinBounds = areaExtent.Width <= tabletSize.Value.Width + && areaExtent.Height <= tabletSize.Value.Height; usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } From 9b70f0ee1fca97f9d8d686c131dd66f5a8be20ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 16:24:51 +0900 Subject: [PATCH 025/120] Tidy up visual appearance of settings and add a reset button --- .../Settings/Sections/Input/TabletSettings.cs | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index ac4a42e984..ca0a0349ab 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -66,6 +66,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input offsetY.MaxValue = val.NewValue.Height; sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + areaSize.Default = new Size(sizeX.Default, sizeY.Default); + updateDisplay(); }, true); } @@ -77,31 +79,44 @@ namespace osu.Game.Overlays.Settings.Sections.Input Children = new Drawable[] { - new SettingsSlider - { - LabelText = "Offset X", - Current = offsetX - }, - new SettingsSlider - { - LabelText = "Offset Y", - Current = offsetY - }, - new SettingsSlider - { - LabelText = "Size X", - Current = sizeX - }, - new SettingsSlider - { - LabelText = "Size Y", - Current = sizeY - }, new TabletAreaSelection(tabletHandler) { RelativeSizeAxes = Axes.X, Height = 300, - } + }, + new SettingsButton + { + Text = "Reset to full area", + Action = () => + { + areaOffset.SetDefault(); + areaSize.SetDefault(); + }, + }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + }, + new SettingsSlider + { + LabelText = "X Offset", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Y Offset", + Current = offsetY + }, + new SettingsSlider + { + LabelText = "Width", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Height", + Current = sizeY + }, }; } } From 43359553c1a3f6326ce13c0080440667ff13548e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 17:04:22 +0900 Subject: [PATCH 026/120] Add aspect ratio display and limiting --- .../Settings/Sections/Input/TabletSettings.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index ca0a0349ab..5d85ecf138 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -23,6 +24,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + private SettingsButton aspectResetButton; + + private readonly BindableNumber aspectRatio = new BindableFloat(1) + { + MinValue = 0.5f, + MaxValue = 2, + Precision = 0.01f, + }; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -48,6 +58,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input { sizeX.Value = val.NewValue.Width; sizeY.Value = val.NewValue.Height; + + float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + + aspectRatio.Value = proposedAspectRatio; + + if (proposedAspectRatio < aspectRatio.MinValue || proposedAspectRatio > aspectRatio.MaxValue) + { + // apply aspect ratio restrictions to keep things in a usable state. + + // correction is always going to be below 1. + float correction = proposedAspectRatio > aspectRatio.Value + ? aspectRatio.Value / proposedAspectRatio + : proposedAspectRatio / aspectRatio.Value; + + if (val.NewValue.Width != val.OldValue.Width) + { + if (val.NewValue.Width > val.OldValue.Width) + correction = 1 / correction; + areaSize.Value = new Size(areaSize.Value.Width, (int)(val.NewValue.Height * correction)); + } + else + { + if (val.NewValue.Height > val.OldValue.Height) + correction = 1 / correction; + areaSize.Value = new Size((int)(val.NewValue.Width * correction), areaSize.Value.Height); + } + } }, true); sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); @@ -97,6 +134,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { LabelText = "Lock aspect ratio", }, + aspectResetButton = new SettingsButton + { + Text = "Take aspect ratio from screen size", + }, + new SettingsSlider + { + LabelText = "Aspect Ratio", + Current = aspectRatio + }, new SettingsSlider { LabelText = "X Offset", From e3bed4c97dff26b9af05609fa100c2d748956b3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 17:57:50 +0900 Subject: [PATCH 027/120] Simplify aspect ratio application, add window conforming and direct adjustment --- .../Settings/Sections/Input/TabletSettings.cs | 120 ++++++++++++------ 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 5d85ecf138..9fa74eda18 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -1,12 +1,14 @@ // 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.ComponentModel; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Platform; +using osu.Framework.Threading; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -21,18 +23,28 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeX = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeY = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; - private SettingsButton aspectResetButton; + [Resolved] + private GameHost host { get; set; } + + /// + /// Based on the longest available smartphone. + /// + private const float largest_feasible_aspect_ratio = 20f / 9; private readonly BindableNumber aspectRatio = new BindableFloat(1) { - MinValue = 0.5f, - MaxValue = 2, + MinValue = 1 / largest_feasible_aspect_ratio, + MaxValue = largest_feasible_aspect_ratio, Precision = 0.01f, }; + private readonly BindableBool aspectLock = new BindableBool(); + + private ScheduledDelegate aspectRatioApplication; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -59,37 +71,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input sizeX.Value = val.NewValue.Width; sizeY.Value = val.NewValue.Height; - float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; - - aspectRatio.Value = proposedAspectRatio; - - if (proposedAspectRatio < aspectRatio.MinValue || proposedAspectRatio > aspectRatio.MaxValue) - { - // apply aspect ratio restrictions to keep things in a usable state. - - // correction is always going to be below 1. - float correction = proposedAspectRatio > aspectRatio.Value - ? aspectRatio.Value / proposedAspectRatio - : proposedAspectRatio / aspectRatio.Value; - - if (val.NewValue.Width != val.OldValue.Width) - { - if (val.NewValue.Width > val.OldValue.Width) - correction = 1 / correction; - areaSize.Value = new Size(areaSize.Value.Width, (int)(val.NewValue.Height * correction)); - } - else - { - if (val.NewValue.Height > val.OldValue.Height) - correction = 1 / correction; - areaSize.Value = new Size((int)(val.NewValue.Width * correction), areaSize.Value.Height); - } - } + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(val)); }, true); sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); + aspectRatio.BindValueChanged(aspect => + { + forceAspectRatio(aspect.NewValue); + }); + ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { @@ -109,6 +102,33 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private void applyAspectRatio(ValueChangedEvent sizeChanged) + { + float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + + if (!aspectLock.Value) + { + aspectRatio.Value = proposedAspectRatio; + + // aspect ratio was in a valid range. + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + return; + } + + if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + { + areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); + } + else + { + areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + } + + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); + } + private void updateDisplay() { if (Children.Count > 0) @@ -121,22 +141,24 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.X, Height = 300, }, - new SettingsButton + new DangerousSettingsButton { Text = "Reset to full area", Action = () => { + aspectLock.Value = false; + areaOffset.SetDefault(); areaSize.SetDefault(); }, }, - new SettingsCheckbox + new SettingsButton { - LabelText = "Lock aspect ratio", - }, - aspectResetButton = new SettingsButton - { - Text = "Take aspect ratio from screen size", + Text = "Conform to current game aspect ratio", + Action = () => + { + forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + } }, new SettingsSlider { @@ -153,6 +175,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Y Offset", Current = offsetY }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + Current = aspectLock + }, new SettingsSlider { LabelText = "Width", @@ -165,5 +192,20 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, }; } + + private void forceAspectRatio(float aspectRatio) + { + aspectLock.Value = false; + + int proposedHeight = (int)(sizeX.Value / aspectRatio); + + if (proposedHeight < sizeY.MaxValue) + sizeY.Value = proposedHeight; + else + sizeX.Value = (int)(sizeY.Value * aspectRatio); + + aspectRatioApplication?.Cancel(); + aspectLock.Value = true; + } } } From 932745e5c4049e8cf15074f5ecca437364d18fd9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:14:29 +0900 Subject: [PATCH 028/120] Fix remaining feedback loops --- .../Settings/Sections/Input/TabletSettings.cs | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 9fa74eda18..e94df7dc1b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.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.ComponentModel; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,7 +79,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatio.BindValueChanged(aspect => { - forceAspectRatio(aspect.NewValue); + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); }); ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); @@ -102,31 +102,44 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; + private void applyAspectRatio(ValueChangedEvent sizeChanged) { - float proposedAspectRatio = (float)sizeX.Value / sizeY.Value; + float proposedAspectRatio = curentAspectRatio; - if (!aspectLock.Value) + try { - aspectRatio.Value = proposedAspectRatio; + if (!aspectLock.Value) + { + // aspect ratio was in a valid range. + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + { + updateAspectRatio(); + return; + } + } - // aspect ratio was in a valid range. - if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) - return; + if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + { + areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); + } + else + { + areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + } } - - if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) + finally { - areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); - } - else - { - areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); } + } - // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. - // this avoids a potential feedback loop. - aspectRatioApplication?.Cancel(); + private void updateAspectRatio() + { + aspectRatio.Value = curentAspectRatio; } private void updateDisplay() @@ -204,6 +217,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input else sizeX.Value = (int)(sizeY.Value * aspectRatio); + updateAspectRatio(); + aspectRatioApplication?.Cancel(); aspectLock.Value = true; } From bba25a0182660443283b91c01356742cae676e56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:40:21 +0900 Subject: [PATCH 029/120] Tidy up draw hierarchy and bindable logic --- .../Sections/Input/TabletAreaSelection.cs | 7 +- .../Settings/Sections/Input/TabletSettings.cs | 184 +++++++++--------- 2 files changed, 95 insertions(+), 96 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 6a3cc46e2b..3a278820f0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly Bindable areaOffset = new BindableSize(); private readonly Bindable areaSize = new BindableSize(); - private readonly Bindable tabletSize = new BindableSize(); + private readonly IBindable tabletSize = new BindableSize(); private OsuSpriteText tabletName; @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input checkBounds(); }, true); - ((IBindable)tabletSize).BindTo(handler.TabletSize); + tabletSize.BindTo(handler.TabletSize); tabletSize.BindValueChanged(val => { tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); @@ -123,6 +123,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input var size = tabletSize.Value; + if (size == System.Drawing.Size.Empty) + return; + float fitX = size.Width / DrawWidth; float fitY = size.Height / DrawHeight; diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index e94df7dc1b..3f8723025f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private readonly BindableSize areaOffset = new BindableSize(); private readonly BindableSize areaSize = new BindableSize(); - private readonly BindableSize tabletSize = new BindableSize(); + private readonly IBindable tabletSize = new BindableSize(); private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; @@ -54,99 +54,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input [BackgroundDependencyLoader] private void load() { - areaOffset.BindTo(tabletHandler.AreaOffset); - areaOffset.BindValueChanged(val => - { - offsetX.Value = val.NewValue.Width; - offsetY.Value = val.NewValue.Height; - }, true); - - offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); - offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); - - areaSize.BindTo(tabletHandler.AreaSize); - areaSize.BindValueChanged(val => - { - sizeX.Value = val.NewValue.Width; - sizeY.Value = val.NewValue.Height; - - aspectRatioApplication?.Cancel(); - aspectRatioApplication = Schedule(() => applyAspectRatio(val)); - }, true); - - sizeX.BindValueChanged(val => areaSize.Value = new Size(val.NewValue, areaSize.Value.Height)); - sizeY.BindValueChanged(val => areaSize.Value = new Size(areaSize.Value.Width, val.NewValue)); - - aspectRatio.BindValueChanged(aspect => - { - aspectRatioApplication?.Cancel(); - aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); - }); - - ((IBindable)tabletSize).BindTo(tabletHandler.TabletSize); - tabletSize.BindValueChanged(val => - { - if (tabletSize.Value == System.Drawing.Size.Empty) - return; - - // todo: these should propagate from a TabletChanged event or similar. - offsetX.MaxValue = val.NewValue.Width; - sizeX.Default = sizeX.MaxValue = val.NewValue.Width; - - offsetY.MaxValue = val.NewValue.Height; - sizeY.Default = sizeY.MaxValue = val.NewValue.Height; - - areaSize.Default = new Size(sizeX.Default, sizeY.Default); - - updateDisplay(); - }, true); - } - - private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; - - private void applyAspectRatio(ValueChangedEvent sizeChanged) - { - float proposedAspectRatio = curentAspectRatio; - - try - { - if (!aspectLock.Value) - { - // aspect ratio was in a valid range. - if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) - { - updateAspectRatio(); - return; - } - } - - if (sizeChanged.NewValue.Width != sizeChanged.OldValue.Width) - { - areaSize.Value = new Size(areaSize.Value.Width, (int)(areaSize.Value.Width / aspectRatio.Value)); - } - else - { - areaSize.Value = new Size((int)(areaSize.Value.Height * aspectRatio.Value), areaSize.Value.Height); - } - } - finally - { - // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. - // this avoids a potential feedback loop. - aspectRatioApplication?.Cancel(); - } - } - - private void updateAspectRatio() - { - aspectRatio.Value = curentAspectRatio; - } - - private void updateDisplay() - { - if (Children.Count > 0) - return; - Children = new Drawable[] { new TabletAreaSelection(tabletHandler) @@ -204,6 +111,91 @@ namespace osu.Game.Overlays.Settings.Sections.Input Current = sizeY }, }; + + areaOffset.BindTo(tabletHandler.AreaOffset); + areaOffset.BindValueChanged(val => + { + offsetX.Value = val.NewValue.Width; + offsetY.Value = val.NewValue.Height; + }, true); + + offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); + offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + + areaSize.BindTo(tabletHandler.AreaSize); + areaSize.BindValueChanged(val => + { + sizeX.Value = val.NewValue.Width; + sizeY.Value = val.NewValue.Height; + }, true); + + sizeX.BindValueChanged(val => + { + areaSize.Value = new Size(val.NewValue, areaSize.Value.Height); + + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(sizeX)); + }); + + sizeY.BindValueChanged(val => + { + areaSize.Value = new Size(areaSize.Value.Width, val.NewValue); + + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); + }); + + aspectRatio.BindValueChanged(aspect => + { + aspectRatioApplication?.Cancel(); + aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); + }); + + tabletSize.BindTo(tabletHandler.TabletSize); + tabletSize.BindValueChanged(val => + { + if (tabletSize.Value == System.Drawing.Size.Empty) + return; + + // todo: these should propagate from a TabletChanged event or similar. + offsetX.MaxValue = val.NewValue.Width; + sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + + offsetY.MaxValue = val.NewValue.Height; + sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + + areaSize.Default = new Size(sizeX.Default, sizeY.Default); + }, true); + } + + private void applyAspectRatio(BindableNumber sizeChanged) + { + try + { + if (!aspectLock.Value) + { + float proposedAspectRatio = curentAspectRatio; + + if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) + { + // aspect ratio was in a valid range. + updateAspectRatio(); + return; + } + } + + // if lock is applied (or the specified values were out of range) aim to adjust the axis the user was not adjusting to conform. + if (sizeChanged == sizeX) + sizeY.Value = (int)(areaSize.Value.Width / aspectRatio.Value); + else + sizeX.Value = (int)(areaSize.Value.Height * aspectRatio.Value); + } + finally + { + // cancel any event which may have fired while updating variables as a result of aspect ratio limitations. + // this avoids a potential feedback loop. + aspectRatioApplication?.Cancel(); + } } private void forceAspectRatio(float aspectRatio) @@ -222,5 +214,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication?.Cancel(); aspectLock.Value = true; } + + private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; + + private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; } } From a8e319a320a1aae74a29432b0794a16ce869330a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 18:51:43 +0900 Subject: [PATCH 030/120] Remove min/max from test scene to fix weirdness when switching test sizings --- osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 3d65db9420..aaf2f13953 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -21,12 +21,7 @@ namespace osu.Game.Tests.Visual.Settings { var tabletHandler = new TestTabletHandler(); - tabletHandler.AreaOffset.MinValue = new Size(0, 0); - tabletHandler.AreaOffset.MaxValue = new Size(160, 100); tabletHandler.AreaOffset.Value = new Size(10, 10); - - tabletHandler.AreaSize.MinValue = new Size(10, 10); - tabletHandler.AreaSize.MaxValue = new Size(160, 100); tabletHandler.AreaSize.Value = new Size(100, 80); AddRange(new Drawable[] From 9a6a0f3df5e8a2d4a67f4bc567ba3e5db8a76a2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:47:08 +0900 Subject: [PATCH 031/120] Add test coverage and better UI handling of no tablet connected scenario --- .../Settings/TestSceneTabletSettings.cs | 10 +- .../Settings/Sections/Input/TabletSettings.cs | 130 +++++++++++------- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index aaf2f13953..2baeadddc0 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; +using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; @@ -39,6 +40,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); + AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(System.Drawing.Size.Empty)); } public class TestTabletHandler : ITabletHandler @@ -48,10 +50,14 @@ namespace osu.Game.Tests.Visual.Settings public BindableSize AreaOffset { get; } = new BindableSize(); public BindableSize AreaSize { get; } = new BindableSize(); public IBindable TabletSize => tabletSize; - public string DeviceName => "test tablet T-421"; + public string DeviceName { get; private set; } public BindableBool Enabled { get; } = new BindableBool(true); - public void SetTabletSize(Size size) => tabletSize.Value = size; + public void SetTabletSize(Size size) + { + DeviceName = size != System.Drawing.Size.Empty ? $"test tablet T-{RNG.Next(999):000}" : string.Empty; + tabletSize.Value = size; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 3f8723025f..7da61cf192 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -5,9 +5,11 @@ using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -44,6 +46,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private ScheduledDelegate aspectRatioApplication; + private FillFlowContainer mainSettings; + + private OsuSpriteText noTabletMessage; + protected override string Header => "Tablet"; public TabletSettings(ITabletHandler tabletHandler) @@ -56,60 +62,77 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Children = new Drawable[] { - new TabletAreaSelection(tabletHandler) + noTabletMessage = new OsuSpriteText { + Text = "No tablet detected!", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + mainSettings = new FillFlowContainer + { + Alpha = 0, RelativeSizeAxes = Axes.X, - Height = 300, - }, - new DangerousSettingsButton - { - Text = "Reset to full area", - Action = () => + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - aspectLock.Value = false; + new TabletAreaSelection(tabletHandler) + { + RelativeSizeAxes = Axes.X, + Height = 300, + Margin = new MarginPadding(10) + }, + new DangerousSettingsButton + { + Text = "Reset to full area", + Action = () => + { + aspectLock.Value = false; - areaOffset.SetDefault(); - areaSize.SetDefault(); - }, - }, - new SettingsButton - { - Text = "Conform to current game aspect ratio", - Action = () => - { - forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + areaOffset.SetDefault(); + areaSize.SetDefault(); + }, + }, + new SettingsButton + { + Text = "Conform to current game aspect ratio", + Action = () => + { + forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height); + } + }, + new SettingsSlider + { + LabelText = "Aspect Ratio", + Current = aspectRatio + }, + new SettingsSlider + { + LabelText = "X Offset", + Current = offsetX + }, + new SettingsSlider + { + LabelText = "Y Offset", + Current = offsetY + }, + new SettingsCheckbox + { + LabelText = "Lock aspect ratio", + Current = aspectLock + }, + new SettingsSlider + { + LabelText = "Width", + Current = sizeX + }, + new SettingsSlider + { + LabelText = "Height", + Current = sizeY + }, } }, - new SettingsSlider - { - LabelText = "Aspect Ratio", - Current = aspectRatio - }, - new SettingsSlider - { - LabelText = "X Offset", - Current = offsetX - }, - new SettingsSlider - { - LabelText = "Y Offset", - Current = offsetY - }, - new SettingsCheckbox - { - LabelText = "Lock aspect ratio", - Current = aspectLock - }, - new SettingsSlider - { - LabelText = "Width", - Current = sizeX - }, - new SettingsSlider - { - LabelText = "Height", - Current = sizeY - }, }; areaOffset.BindTo(tabletHandler.AreaOffset); @@ -154,8 +177,17 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletSize.BindTo(tabletHandler.TabletSize); tabletSize.BindValueChanged(val => { - if (tabletSize.Value == System.Drawing.Size.Empty) + bool tabletFound = tabletSize.Value != System.Drawing.Size.Empty; + + if (!tabletFound) + { + mainSettings.Hide(); + noTabletMessage.Show(); return; + } + + mainSettings.Show(); + noTabletMessage.Hide(); // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = val.NewValue.Width; From d422a6590036dc26a881fa2519da4d09111ac479 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:47:18 +0900 Subject: [PATCH 032/120] Fix initial tablet size not being initialised --- .../Settings/Sections/Input/TabletAreaSelection.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 3a278820f0..af144c8102 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -80,6 +80,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, } }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => @@ -101,7 +106,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); tabletName.Text = handler.DeviceName; checkBounds(); - }); + }, true); } [Resolved] From 9d0c8902a6e6dcc1edc369b8ebf768089f22c8a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Mar 2021 23:57:05 +0900 Subject: [PATCH 033/120] Fix margins and spacing between sub flowed items --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 6 +++--- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index af144c8102..3b1bae7cf0 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -32,13 +32,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; + + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }; } [BackgroundDependencyLoader] private void load() { - Padding = new MarginPadding(5); - InternalChild = tabletContainer = new Container { Anchor = Anchor.Centre, @@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (size == System.Drawing.Size.Empty) return; - float fitX = size.Width / DrawWidth; + float fitX = size.Width / (DrawWidth - Padding.Left - Padding.Right); float fitY = size.Height / DrawHeight; float adjust = MathF.Max(fitX, fitY); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 7da61cf192..b17cfced95 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -10,6 +10,7 @@ using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Input { @@ -67,12 +68,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input Text = "No tablet detected!", Anchor = Anchor.Centre, Origin = Anchor.Centre, + Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS } }, mainSettings = new FillFlowContainer { Alpha = 0, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 8), Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -80,7 +83,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input { RelativeSizeAxes = Axes.X, Height = 300, - Margin = new MarginPadding(10) }, new DangerousSettingsButton { From 196f95ae545853c00e3da66ba9bc20e4871c75a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 12:50:02 +0900 Subject: [PATCH 034/120] Update to use new bindables and centered area offset --- .../Settings/TestSceneTabletSettings.cs | 44 +++++++----- .../Sections/Input/TabletAreaSelection.cs | 49 ++++++++------ .../Settings/Sections/Input/TabletSettings.cs | 67 ++++++++++--------- 3 files changed, 89 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 2baeadddc0..a7f6c8c0d3 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.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.Drawing; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -11,6 +10,7 @@ using osu.Framework.Platform; using osu.Framework.Utils; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; +using osuTK; namespace osu.Game.Tests.Visual.Settings { @@ -22,9 +22,6 @@ namespace osu.Game.Tests.Visual.Settings { var tabletHandler = new TestTabletHandler(); - tabletHandler.AreaOffset.Value = new Size(10, 10); - tabletHandler.AreaSize.Value = new Size(100, 80); - AddRange(new Drawable[] { new TabletSettings(tabletHandler) @@ -36,27 +33,40 @@ namespace osu.Game.Tests.Visual.Settings } }); - AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Size(160, 100))); - AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Size(300, 300))); - AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 300))); - AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Size(100, 700))); - AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(System.Drawing.Size.Empty)); + AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100))); + AddStep("Test with square tablet", () => tabletHandler.SetTabletSize(new Vector2(300, 300))); + AddStep("Test with tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 300))); + AddStep("Test with very tall tablet", () => tabletHandler.SetTabletSize(new Vector2(100, 700))); + AddStep("Test no tablet present", () => tabletHandler.SetTabletSize(Vector2.Zero)); } public class TestTabletHandler : ITabletHandler { - private readonly Bindable tabletSize = new Bindable(); + public Bindable AreaOffset { get; } = new Bindable(); + public Bindable AreaSize { get; } = new Bindable(); + + public IBindable Tablet => tablet; + + private readonly Bindable tablet = new Bindable(); - public BindableSize AreaOffset { get; } = new BindableSize(); - public BindableSize AreaSize { get; } = new BindableSize(); - public IBindable TabletSize => tabletSize; - public string DeviceName { get; private set; } public BindableBool Enabled { get; } = new BindableBool(true); - public void SetTabletSize(Size size) + public void SetTabletSize(Vector2 size) { - DeviceName = size != System.Drawing.Size.Empty ? $"test tablet T-{RNG.Next(999):000}" : string.Empty; - tabletSize.Value = size; + tablet.Value = size != Vector2.Zero ? new TabletInfo($"test tablet T-{RNG.Next(999):000}", size) : null; + + AreaSize.Default = new Vector2(size.X, size.Y); + + // if it's clear the user has not configured the area, take the full area from the tablet that was just found. + if (AreaSize.Value == Vector2.Zero) + AreaSize.SetDefault(); + + AreaOffset.Default = new Vector2(size.X / 2, size.Y / 2); + + // likewise with the position, use the centre point if it has not been configured. + // it's safe to assume no user would set their centre point to 0,0 for now. + if (AreaOffset.Value == Vector2.Zero) + AreaOffset.SetDefault(); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 3b1bae7cf0..c0412fb99d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -23,9 +22,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Container tabletContainer; private Container usableAreaContainer; - private readonly Bindable areaOffset = new BindableSize(); - private readonly Bindable areaSize = new BindableSize(); - private readonly IBindable tabletSize = new BindableSize(); + private readonly Bindable areaOffset = new Bindable(); + private readonly Bindable areaSize = new Bindable(); + + private readonly IBindable tablet = new Bindable(); private OsuSpriteText tabletName; @@ -56,6 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, usableAreaContainer = new Container { + Origin = Anchor.Centre, Children = new Drawable[] { new Box @@ -89,24 +90,27 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(handler.AreaOffset); areaOffset.BindValueChanged(val => { - usableAreaContainer.MoveTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); - checkBounds(); + usableAreaContainer.MoveTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. }, true); areaSize.BindTo(handler.AreaSize); areaSize.BindValueChanged(val => { - usableAreaContainer.ResizeTo(new Vector2(val.NewValue.Width, val.NewValue.Height), 100, Easing.OutQuint); + usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) + .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + }, true); + + tablet.BindTo(handler.Tablet); + tablet.BindValueChanged(val => + { + tabletContainer.Size = val.NewValue?.Size ?? Vector2.Zero; + tabletName.Text = val.NewValue?.Name ?? string.Empty; checkBounds(); }, true); - tabletSize.BindTo(handler.TabletSize); - tabletSize.BindValueChanged(val => - { - tabletContainer.Size = new Vector2(val.NewValue.Width, val.NewValue.Height); - tabletName.Text = handler.DeviceName; - checkBounds(); - }, true); + // initial animation should be instant. + FinishTransforms(true); } [Resolved] @@ -114,10 +118,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void checkBounds() { - Size areaExtent = areaOffset.Value + areaSize.Value; + if (tablet.Value == null) + return; - bool isWithinBounds = areaExtent.Width <= tabletSize.Value.Width - && areaExtent.Height <= tabletSize.Value.Height; + var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; + + bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } @@ -126,13 +133,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input { base.Update(); - var size = tabletSize.Value; - - if (size == System.Drawing.Size.Empty) + if (!(tablet.Value?.Size is Vector2 size)) return; - float fitX = size.Width / (DrawWidth - Padding.Left - Padding.Right); - float fitY = size.Height / DrawHeight; + float fitX = size.X / (DrawWidth - Padding.Left - Padding.Right); + float fitY = size.Y / DrawHeight; float adjust = MathF.Max(fitX, fitY); diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b17cfced95..9f81391434 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.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.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,15 +17,15 @@ namespace osu.Game.Overlays.Settings.Sections.Input { private readonly ITabletHandler tabletHandler; - private readonly BindableSize areaOffset = new BindableSize(); - private readonly BindableSize areaSize = new BindableSize(); - private readonly IBindable tabletSize = new BindableSize(); + private readonly Bindable areaOffset = new Bindable(); + private readonly Bindable areaSize = new Bindable(); + private readonly IBindable tablet = new Bindable(); - private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetX = new BindableNumber { MinValue = 0 }; + private readonly BindableNumber offsetY = new BindableNumber { MinValue = 0 }; - private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; - private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeX = new BindableNumber { MinValue = 10 }; + private readonly BindableNumber sizeY = new BindableNumber { MinValue = 10 }; [Resolved] private GameHost host { get; set; } @@ -108,12 +107,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Aspect Ratio", Current = aspectRatio }, - new SettingsSlider + new SettingsSlider { LabelText = "X Offset", Current = offsetX }, - new SettingsSlider + new SettingsSlider { LabelText = "Y Offset", Current = offsetY @@ -123,12 +122,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = "Lock aspect ratio", Current = aspectLock }, - new SettingsSlider + new SettingsSlider { LabelText = "Width", Current = sizeX }, - new SettingsSlider + new SettingsSlider { LabelText = "Height", Current = sizeY @@ -140,23 +139,23 @@ namespace osu.Game.Overlays.Settings.Sections.Input areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => { - offsetX.Value = val.NewValue.Width; - offsetY.Value = val.NewValue.Height; + offsetX.Value = val.NewValue.X; + offsetY.Value = val.NewValue.Y; }, true); - offsetX.BindValueChanged(val => areaOffset.Value = new Size(val.NewValue, areaOffset.Value.Height)); - offsetY.BindValueChanged(val => areaOffset.Value = new Size(areaOffset.Value.Width, val.NewValue)); + offsetX.BindValueChanged(val => areaOffset.Value = new Vector2(val.NewValue, areaOffset.Value.Y)); + offsetY.BindValueChanged(val => areaOffset.Value = new Vector2(areaOffset.Value.X, val.NewValue)); areaSize.BindTo(tabletHandler.AreaSize); areaSize.BindValueChanged(val => { - sizeX.Value = val.NewValue.Width; - sizeY.Value = val.NewValue.Height; + sizeX.Value = val.NewValue.X; + sizeY.Value = val.NewValue.Y; }, true); sizeX.BindValueChanged(val => { - areaSize.Value = new Size(val.NewValue, areaSize.Value.Height); + areaSize.Value = new Vector2(val.NewValue, areaSize.Value.Y); aspectRatioApplication?.Cancel(); aspectRatioApplication = Schedule(() => applyAspectRatio(sizeX)); @@ -164,7 +163,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input sizeY.BindValueChanged(val => { - areaSize.Value = new Size(areaSize.Value.Width, val.NewValue); + areaSize.Value = new Vector2(areaSize.Value.X, val.NewValue); aspectRatioApplication?.Cancel(); aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); @@ -176,10 +175,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication = Schedule(() => forceAspectRatio(aspect.NewValue)); }); - tabletSize.BindTo(tabletHandler.TabletSize); - tabletSize.BindValueChanged(val => + tablet.BindTo(tabletHandler.Tablet); + tablet.BindValueChanged(val => { - bool tabletFound = tabletSize.Value != System.Drawing.Size.Empty; + var tab = val.NewValue; + + bool tabletFound = tab != null; if (!tabletFound) { @@ -192,17 +193,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input noTabletMessage.Hide(); // todo: these should propagate from a TabletChanged event or similar. - offsetX.MaxValue = val.NewValue.Width; - sizeX.Default = sizeX.MaxValue = val.NewValue.Width; + offsetX.MaxValue = tab.Size.X; + offsetX.Default = tab.Size.X / 2; + sizeX.Default = sizeX.MaxValue = tab.Size.X; - offsetY.MaxValue = val.NewValue.Height; - sizeY.Default = sizeY.MaxValue = val.NewValue.Height; + offsetY.MaxValue = tab.Size.Y; + offsetY.Default = tab.Size.Y / 2; + sizeY.Default = sizeY.MaxValue = tab.Size.Y; - areaSize.Default = new Size(sizeX.Default, sizeY.Default); + areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); }, true); } - private void applyAspectRatio(BindableNumber sizeChanged) + private void applyAspectRatio(BindableNumber sizeChanged) { try { @@ -220,9 +223,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input // if lock is applied (or the specified values were out of range) aim to adjust the axis the user was not adjusting to conform. if (sizeChanged == sizeX) - sizeY.Value = (int)(areaSize.Value.Width / aspectRatio.Value); + sizeY.Value = (int)(areaSize.Value.X / aspectRatio.Value); else - sizeX.Value = (int)(areaSize.Value.Height * aspectRatio.Value); + sizeX.Value = (int)(areaSize.Value.Y * aspectRatio.Value); } finally { @@ -251,6 +254,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; - private float curentAspectRatio => (float)sizeX.Value / sizeY.Value; + private float curentAspectRatio => sizeX.Value / sizeY.Value; } } From fb7d095e4a0a934664db0a5cbc7585eea55b46da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:03:06 +0900 Subject: [PATCH 035/120] Show aspect ratio for current usable area --- .../Sections/Input/TabletAreaSelection.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index c0412fb99d..ba219cfe7d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -29,6 +29,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private OsuSpriteText tabletName; + private Box usableFill; + private OsuSpriteText usableAreaText; + public TabletAreaSelection(ITabletHandler handler) { this.handler = handler; @@ -59,17 +62,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input Origin = Anchor.Centre, Children = new Drawable[] { - new Box + usableFill = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0.6f, }, - new OsuSpriteText + usableAreaText = new OsuSpriteText { - Text = "usable area", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = Color4.Black, + Colour = Color4.White, Font = OsuFont.Default.With(size: 12) } } @@ -99,6 +101,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input { usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint) .OnComplete(_ => checkBounds()); // required as we are using SSDQ. + + int x = (int)val.NewValue.X; + int y = (int)val.NewValue.Y; + int commonDivider = greatestCommonDivider(x, y); + + usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; }, true); tablet.BindTo(handler.Tablet); @@ -113,6 +121,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input FinishTransforms(true); } + private static int greatestCommonDivider(int a, int b) + { + while (b != 0) + { + int remainder = a % b; + a = b; + b = remainder; + } + + return a; + } + [Resolved] private OsuColour colour { get; set; } @@ -126,7 +146,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); - usableAreaContainer.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); + usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } protected override void Update() From e8c20bdcb12a3a1f513a6fa63f72511630799afe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:05:43 +0900 Subject: [PATCH 036/120] Add centre crosshair --- .../Sections/Input/TabletAreaSelection.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index ba219cfe7d..0a44b1a44d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -67,12 +67,27 @@ namespace osu.Game.Overlays.Settings.Sections.Input RelativeSizeAxes = Axes.Both, Alpha = 0.6f, }, + new Box + { + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 5, + }, + new Box + { + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 5, + }, usableAreaText = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = Color4.White, - Font = OsuFont.Default.With(size: 12) + Font = OsuFont.Default.With(size: 12), + Y = 10 } } }, From 6285dcd1a1d9989dd9daeeb685102b48472e4ba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:09:15 +0900 Subject: [PATCH 037/120] Add arbitrary value to fix FP contains check failures --- .../Overlays/Settings/Sections/Input/TabletAreaSelection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index 0a44b1a44d..df25668411 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -158,8 +158,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input var usableSsdq = usableAreaContainer.ScreenSpaceDrawQuad; - bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft) && - tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight); + bool isWithinBounds = tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.TopLeft + new Vector2(1)) && + tabletContainer.ScreenSpaceDrawQuad.Contains(usableSsdq.BottomRight - new Vector2(1)); usableFill.FadeColour(isWithinBounds ? colour.Blue : colour.RedLight, 100); } From c624aa939774cc3f783eb2a588d96c8afefc37f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 13:23:23 +0900 Subject: [PATCH 038/120] Only update tablet values on commit --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 9f81391434..b2d37e345f 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -104,16 +104,19 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Aspect Ratio", Current = aspectRatio }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "X Offset", Current = offsetX }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Y Offset", Current = offsetY }, @@ -124,11 +127,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Width", Current = sizeX }, new SettingsSlider { + TransferValueOnCommit = true, LabelText = "Height", Current = sizeY }, From b1c4ac9f42842943d451f13b57da44231b34431d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:13:53 +0900 Subject: [PATCH 039/120] Remove local implementation of Vector2Converter This has been moved to framework in https://github.com/ppy/osu-framework/pull/4285. --- .../Converters/Vector2Converter.cs | 34 ------------------- .../IO/Serialization/IJsonSerializable.cs | 2 -- 2 files changed, 36 deletions(-) delete mode 100644 osu.Game/IO/Serialization/Converters/Vector2Converter.cs diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs deleted file mode 100644 index 46447b607b..0000000000 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ /dev/null @@ -1,34 +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 Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using osuTK; - -namespace osu.Game.IO.Serialization.Converters -{ - /// - /// A type of that serializes only the X and Y coordinates of a . - /// - public class Vector2Converter : JsonConverter - { - public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var obj = JObject.Load(reader); - return new Vector2((float)obj["x"], (float)obj["y"]); - } - - public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WritePropertyName("x"); - writer.WriteValue(value.X); - writer.WritePropertyName("y"); - writer.WriteValue(value.Y); - - writer.WriteEndObject(); - } - } -} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index ac95d47c4b..30430e6f7f 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; -using osu.Game.IO.Serialization.Converters; namespace osu.Game.IO.Serialization { @@ -28,7 +27,6 @@ namespace osu.Game.IO.Serialization Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - Converters = new JsonConverter[] { new Vector2Converter() }, ContractResolver = new KeyContractResolver() }; } From 1e82033c840725d4facd32a95be7d7ebe682e908 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:18:56 +0900 Subject: [PATCH 040/120] Move bindings to LoadComplete to avoid cross-thread issues --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b2d37e345f..19a3c5b6f2 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -140,6 +140,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input } }, }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); areaOffset.BindTo(tabletHandler.AreaOffset); areaOffset.BindValueChanged(val => From fefb0078056fcfada6602ca1396318b618767bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Mar 2021 14:19:15 +0900 Subject: [PATCH 041/120] Remove no longer relevant comment --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 19a3c5b6f2..d0e3ddbd91 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -202,7 +202,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input mainSettings.Show(); noTabletMessage.Hide(); - // todo: these should propagate from a TabletChanged event or similar. offsetX.MaxValue = tab.Size.X; offsetX.Default = tab.Size.X / 2; sizeX.Default = sizeX.MaxValue = tab.Size.X; From bd1e2da1c2260457234bf40d16af2f130f68765a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:09:39 +0900 Subject: [PATCH 042/120] Always hide other overlays, even if the new one is not loaded --- 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 e5e1f6946e..e2f0f0c05b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -756,12 +756,12 @@ namespace osu.Game private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays) { + otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); + // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. if (overlay.LoadState < LoadState.Ready) return; - otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); - // show above others if not visible at all, else leave at current depth. if (!overlay.IsPresent) overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); From c0c8b3e46c49290709a8c9948693c72857404704 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:22:47 +0900 Subject: [PATCH 043/120] Fix regression meaning `SkinnableSound` initialisation may never happen --- osu.Game/Skinning/SkinnableSound.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index e447f9c44c..9c6a4f7970 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -131,6 +131,14 @@ namespace osu.Game.Skinning }); } + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); + + if (!samplesContainer.Any()) + updateSamples(); + } + /// /// Stops the samples. /// From 9be7981e0d981cea5ed9b605ab9423e77877b7f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 19:45:00 +0900 Subject: [PATCH 044/120] Adjust timeline ticks to be more visible --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 3 +-- .../Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- .../Components/Timeline/TimelineTickDisplay.cs | 13 +++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index d9477dd4bc..ff33f0c70d 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osuTK.Graphics; @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Edit /// The beat divisor. /// The set of colours. /// The applicable colour from for . - public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + public static Color4 GetColourFor(int beatDivisor, OsuColour colours) { switch (beatDivisor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 8a92a2011d..59f88ac641 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours); int repeatIndex = placementIndex / beatDivisor.Value; - return colour.MultiplyAlpha(0.5f / (repeatIndex + 1)); + return ColourInfo.SingleColour(colour).MultiplyAlpha(0.5f / (repeatIndex + 1)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index fb11b859a7..c070c833f8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -6,7 +6,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -124,25 +126,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (beat == 0 && i == 0) nextMinTick = float.MinValue; - var indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); + int indexInBar = beat % ((int)point.TimeSignature * beatDivisor.Value); var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); var colour = BindableBeatDivisor.GetColourFor(divisor, colours); + bool isMainBeat = indexInBar == 0; + // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. - var height = indexInBar == 0 ? 0.5f : 0.1f - (float)divisor / highestDivisor * 0.08f; + float height = isMainBeat ? 0.5f : 0.4f - (float)divisor / highestDivisor * 0.2f; + float gradientOpacity = isMainBeat ? 1 : 0; var topPoint = getNextUsablePoint(); topPoint.X = xPos; - topPoint.Colour = colour; topPoint.Height = height; + topPoint.Colour = ColourInfo.GradientVertical(colour, colour.Opacity(gradientOpacity)); topPoint.Anchor = Anchor.TopLeft; topPoint.Origin = Anchor.TopCentre; var bottomPoint = getNextUsablePoint(); bottomPoint.X = xPos; - bottomPoint.Colour = colour; bottomPoint.Anchor = Anchor.BottomLeft; + bottomPoint.Colour = ColourInfo.GradientVertical(colour.Opacity(gradientOpacity), colour); bottomPoint.Origin = Anchor.BottomCentre; bottomPoint.Height = height; } From 8955071703d40ed9f0210a45def45546bf1c19cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:01:45 +0900 Subject: [PATCH 045/120] Change editor speed adjust to adjust frequency --- osu.Game/Screens/Edit/Components/PlaybackControl.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 9739f2876a..bdc6e238c8 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Components [Resolved] private EditorClock editorClock { get; set; } - private readonly BindableNumber tempo = new BindableDouble(1); + private readonly BindableNumber freqAdjust = new BindableDouble(1); [BackgroundDependencyLoader] private void load() @@ -58,16 +58,16 @@ namespace osu.Game.Screens.Edit.Components RelativeSizeAxes = Axes.Both, Height = 0.5f, Padding = new MarginPadding { Left = 45 }, - Child = new PlaybackTabControl { Current = tempo }, + Child = new PlaybackTabControl { Current = freqAdjust }, } }; - Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempo), true); + Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Frequency, freqAdjust), true); } protected override void Dispose(bool isDisposing) { - Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempo); + Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, freqAdjust); base.Dispose(isDisposing); } @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Edit.Components private class PlaybackTabControl : OsuTabControl { - private static readonly double[] tempo_values = { 0.5, 0.75, 1 }; + private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 }; protected override TabItem CreateTabItem(double value) => new PlaybackTabItem(value); From 86b229b1c94a1e0e3379b4e013206fd5ac996771 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:05:18 +0900 Subject: [PATCH 046/120] Increase maximum usable aspect ratio to account for ultrawide monitors --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index d0e3ddbd91..73ac8e4f35 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -31,9 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private GameHost host { get; set; } /// - /// Based on the longest available smartphone. + /// Based on ultrawide monitor configurations. /// - private const float largest_feasible_aspect_ratio = 20f / 9; + private const float largest_feasible_aspect_ratio = 21f / 9; private readonly BindableNumber aspectRatio = new BindableFloat(1) { From 4795170c6044bc7092b4c78b687f5a5bdc880088 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:07:13 +0900 Subject: [PATCH 047/120] Add back the default json converter locally to ensure it's actually used --- osu.Game/IO/Serialization/IJsonSerializable.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 30430e6f7f..ba188963ea 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -1,7 +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.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.IO.Serialization; namespace osu.Game.IO.Serialization { @@ -27,6 +29,7 @@ namespace osu.Game.IO.Serialization Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + Converters = new List { new Vector2Converter() }, ContractResolver = new KeyContractResolver() }; } From 095b7f86685799249f5427ba7010f55a6a7e6646 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:09:12 +0900 Subject: [PATCH 048/120] Rewrite code to account for non-loaded edge case --- osu.Game/OsuGame.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e2f0f0c05b..be919d60ca 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -758,13 +758,15 @@ namespace osu.Game { otherOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); - // generally shouldn't ever hit this state, but protects against a crash on attempting to change ChildDepth. - if (overlay.LoadState < LoadState.Ready) + // Partially visible so leave it at the current depth. + if (overlay.IsPresent) return; - // show above others if not visible at all, else leave at current depth. - if (!overlay.IsPresent) + // Show above all other overlays. + if (overlay.IsLoaded) overlayContent.ChangeChildDepth(overlay, (float)-Clock.CurrentTime); + else + overlay.Depth = (float)-Clock.CurrentTime; } private void forwardLoggedErrorsToNotifications() From 6f32c302eb692f2a8c7bf6122149a28d96672446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:13:51 +0900 Subject: [PATCH 049/120] Add checkbox to optionally disable tablet handling --- .../Settings/Sections/Input/TabletSettings.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 73ac8e4f35..893fe575cd 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -62,11 +62,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input { Children = new Drawable[] { + new SettingsCheckbox + { + LabelText = "Enabled", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Current = tabletHandler.Enabled + }, noTabletMessage = new OsuSpriteText { Text = "No tablet detected!", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS } }, mainSettings = new FillFlowContainer From 63cbac3bd059778a8b94ad0d5e5b47beab637f33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:15:29 +0900 Subject: [PATCH 050/120] Ensure aspect ratio slider gets an initial value --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 893fe575cd..b06b148984 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -186,6 +186,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectRatioApplication = Schedule(() => applyAspectRatio(sizeY)); }); + updateAspectRatio(); aspectRatio.BindValueChanged(aspect => { aspectRatioApplication?.Cancel(); From b2d8db3a92d65c2db7a26d08f276a30fef82c7c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:25:21 +0900 Subject: [PATCH 051/120] Rename incorrect variable --- osu.Game/Skinning/PoolableSkinnableSample.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index 09e087d0f2..c01a6d20cc 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -94,21 +94,21 @@ namespace osu.Game.Skinning sampleContainer.Clear(); Sample = null; - var ch = CurrentSkin.GetSample(sampleInfo); + var sample = CurrentSkin.GetSample(sampleInfo); - if (ch == null && AllowDefaultFallback) + if (sample == null && AllowDefaultFallback) { foreach (var lookup in sampleInfo.LookupNames) { - if ((ch = sampleStore.Get(lookup)) != null) + if ((sample = sampleStore.Get(lookup)) != null) break; } } - if (ch == null) + if (sample == null) return; - sampleContainer.Add(Sample = new DrawableSample(ch)); + sampleContainer.Add(Sample = new DrawableSample(sample)); // Start playback internally for the new sample if the previous one was playing beforehand. if (wasPlaying && Looping) From 68aaf90702458f288913e191c4d27c342aa7a358 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Mar 2021 20:30:52 +0900 Subject: [PATCH 052/120] Fix disposal rather than performing some weird hack --- osu.Game/Skinning/LegacySkin.cs | 4 +++- osu.Game/Skinning/PoolableSkinnableSample.cs | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index b69e99773c..ec49d43c67 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -509,7 +509,7 @@ namespace osu.Game.Skinning /// /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. /// - private class LegacySkinSample : ISample + private class LegacySkinSample : ISample, IDisposable { private readonly Sample sample; @@ -575,6 +575,8 @@ namespace osu.Game.Skinning public IBindable AggregateFrequency => sample.AggregateFrequency; public IBindable AggregateTempo => sample.AggregateTempo; + + public void Dispose() => sample.Dispose(); } } } diff --git a/osu.Game/Skinning/PoolableSkinnableSample.cs b/osu.Game/Skinning/PoolableSkinnableSample.cs index c01a6d20cc..b04158a58f 100644 --- a/osu.Game/Skinning/PoolableSkinnableSample.cs +++ b/osu.Game/Skinning/PoolableSkinnableSample.cs @@ -83,14 +83,6 @@ namespace osu.Game.Skinning bool wasPlaying = Playing; - if (activeChannel != null) - { - // when switching away from previous samples, we don't want to call Stop() on them as it sounds better to let them play out. - // this may change in the future if we use PoolableSkinSample in more locations than gameplay. - // we *do* want to turn off looping, else we end up with an infinite looping sample running in the background. - activeChannel.Looping = false; - } - sampleContainer.Clear(); Sample = null; From 9634560d4b761a2a3aa90d78ffb1933e2c96c7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 21:36:28 +0100 Subject: [PATCH 053/120] Fix control point visualiser crashing after deselections `SliderSelectionBlueprint.OnDeselected()` would expire the `ControlPointVisualiser` on deselection, leading to its removal from the blueprint and eventual disposal, but still kept a separate reference to said visualiser in another field. This could lead to that stale reference to a disposed child getting read in `ReceivePositionalInputAt()`, crashing quite a ways down over at the framework side on futilely trying to compute the bounding box of a drawable with no parent. --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 3d3dff653a..befe3c6695 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -114,6 +114,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // throw away frame buffers on deselection. ControlPointVisualiser?.Expire(); + ControlPointVisualiser = null; + BodyPiece.RecyclePath(); } From e67c759eef36a409e4b57ab1c7643eb307280cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:44:31 +0100 Subject: [PATCH 054/120] Mark control point visualiser as possibly-null --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index befe3c6695..ba9bb3c485 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected SliderBodyPiece BodyPiece { get; private set; } protected SliderCircleSelectionBlueprint HeadBlueprint { get; private set; } protected SliderCircleSelectionBlueprint TailBlueprint { get; private set; } + + [CanBeNull] protected PathControlPointVisualiser ControlPointVisualiser { get; private set; } private readonly DrawableSlider slider; From 8e0536e1e2d2eb66a7572dcf27f19d65cb4f5e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:20:26 +0100 Subject: [PATCH 055/120] Add failing test scene --- .../Editing/TestSceneBlueprintSelection.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs new file mode 100644 index 0000000000..fd9c09fd5f --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneBlueprintSelection : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private BlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); + + [Test] + public void TestSelectedObjectHasPriorityWhenOverlapping() + { + var firstSlider = new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2()), + new PathControlPoint(new Vector2(150, -50)), + new PathControlPoint(new Vector2(300, 0)) + }), + Position = new Vector2(0, 100) + }; + var secondSlider = new Slider + { + Path = new SliderPath(new[] + { + new PathControlPoint(new Vector2()), + new PathControlPoint(new Vector2(-50, 50)), + new PathControlPoint(new Vector2(-100, 100)) + }), + Position = new Vector2(200, 0) + }; + + AddStep("add overlapping sliders", () => + { + EditorBeatmap.Add(firstSlider); + EditorBeatmap.Add(secondSlider); + }); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.Add(firstSlider)); + + AddStep("move mouse to common point", () => + { + var pos = blueprintContainer.ChildrenOfType().ElementAt(1).ScreenSpaceDrawQuad.Centre; + InputManager.MoveMouseTo(pos); + }); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + + AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider); + } + } +} From dd48b68f8ad6fc513d17f9847d0863df2c90dc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Mar 2021 22:20:40 +0100 Subject: [PATCH 056/120] Ensure selected blueprints are given selection priority --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 051d0766bf..7def7e1d16 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -338,7 +338,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private bool beginClickSelection(MouseButtonEvent e) { // Iterate from the top of the input stack (blueprints closest to the front of the screen first). - foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse()) + // Priority is given to already-selected blueprints. + foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Reverse().OrderByDescending(b => b.IsSelected)) { if (!blueprint.IsHovered) continue; From ca943a897a387e7a7f7d8a934aee7125f262f698 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Mar 2021 10:51:58 +0900 Subject: [PATCH 057/120] Fix back to front initialisation order --- osu.Game/Skinning/SkinnableSound.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 9c6a4f7970..f935adf7a5 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -133,10 +133,11 @@ namespace osu.Game.Skinning protected override void LoadAsyncComplete() { - base.LoadAsyncComplete(); - + // ensure samples are constructed before SkinChanged() is called via base.LoadAsyncComplete(). if (!samplesContainer.Any()) updateSamples(); + + base.LoadAsyncComplete(); } /// From d28bed6ed29f9aeea901ccbec2c075367007fb47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Mar 2021 12:29:24 +0100 Subject: [PATCH 058/120] Schedule adding transforms on tablet changes Fixes `InvalidThreadForMutationException`s that pop up when disconnecting/reconnecting tablets during the game's operation. In those cases the value change callback executes from an OpenTabletDriver thread. --- .../Sections/Input/TabletAreaSelection.cs | 15 ++++++----- .../Settings/Sections/Input/TabletSettings.cs | 25 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs index df25668411..ecb8acce54 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletAreaSelection.cs @@ -125,17 +125,20 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); tablet.BindTo(handler.Tablet); - tablet.BindValueChanged(val => - { - tabletContainer.Size = val.NewValue?.Size ?? Vector2.Zero; - tabletName.Text = val.NewValue?.Name ?? string.Empty; - checkBounds(); - }, true); + tablet.BindValueChanged(_ => Scheduler.AddOnce(updateTabletDetails)); + updateTabletDetails(); // initial animation should be instant. FinishTransforms(true); } + private void updateTabletDetails() + { + tabletContainer.Size = tablet.Value?.Size ?? Vector2.Zero; + tabletName.Text = tablet.Value?.Name ?? string.Empty; + checkBounds(); + } + private static int greatestCommonDivider(int a, int b) { while (b != 0) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index b06b148984..4baf43783d 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -196,19 +196,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input tablet.BindTo(tabletHandler.Tablet); tablet.BindValueChanged(val => { + Scheduler.AddOnce(toggleVisibility); + var tab = val.NewValue; bool tabletFound = tab != null; - if (!tabletFound) - { - mainSettings.Hide(); - noTabletMessage.Show(); return; - } - - mainSettings.Show(); - noTabletMessage.Hide(); offsetX.MaxValue = tab.Size.X; offsetX.Default = tab.Size.X / 2; @@ -222,6 +216,21 @@ namespace osu.Game.Overlays.Settings.Sections.Input }, true); } + private void toggleVisibility() + { + bool tabletFound = tablet.Value != null; + + if (!tabletFound) + { + mainSettings.Hide(); + noTabletMessage.Show(); + return; + } + + mainSettings.Show(); + noTabletMessage.Hide(); + } + private void applyAspectRatio(BindableNumber sizeChanged) { try From 86b569f5f7a13700b7ef728fafb6fe1c014ea8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 20 Mar 2021 12:34:41 +0100 Subject: [PATCH 059/120] Fix typo in identifier --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 4baf43783d..bd0f7ddc4c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { if (!aspectLock.Value) { - float proposedAspectRatio = curentAspectRatio; + float proposedAspectRatio = currentAspectRatio; if (proposedAspectRatio >= aspectRatio.MinValue && proposedAspectRatio <= aspectRatio.MaxValue) { @@ -278,8 +278,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input aspectLock.Value = true; } - private void updateAspectRatio() => aspectRatio.Value = curentAspectRatio; + private void updateAspectRatio() => aspectRatio.Value = currentAspectRatio; - private float curentAspectRatio => sizeX.Value / sizeY.Value; + private float currentAspectRatio => sizeX.Value / sizeY.Value; } } From a16c0641b27ab69f59c56a4cf96f388e941affa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 11:01:06 +0100 Subject: [PATCH 060/120] Revert EF Core to version 2.2 This reverts commit f3faad74d587bbbd104395f5072723203c9d54aa, reversing changes made to 712e7bc7bfaa94dd8c7248d9e800e7bc916476c0. Several issues arose after migrating to 5.0, including, but possibly not limited to, performance regressions in song select, as well as failures when attempting to save beatmaps after metadata changes in the editor. --- osu.Desktop/osu.Desktop.csproj | 4 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 1 - .../TestSceneBeatmapRecommendations.cs | 1 - .../SongSelect/TestScenePlaySongSelect.cs | 4 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 - osu.Game/Database/ArchiveModelManager.cs | 6 +- .../Database/DatabaseWorkaroundExtensions.cs | 71 ------------------- osu.Game/Database/OsuDbContext.cs | 9 +-- osu.Game/Scoring/ScoreInfo.cs | 13 +++- osu.Game/Scoring/ScoreManager.cs | 5 -- osu.Game/Skinning/SkinManager.cs | 5 -- osu.Game/osu.Game.csproj | 6 +- osu.iOS.props | 2 + 18 files changed, 30 insertions(+), 109 deletions(-) delete mode 100644 osu.Game/Database/DatabaseWorkaroundExtensions.cs diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index d9d23dea6b..3e0f0cb7f6 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -27,8 +27,8 @@ - - + + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 42f70151ac..728af5124e 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index e51b20c9fe..af16f39563 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f1f75148ef..3d2d1f3fec 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + WinExe 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 c9a320bdd5..fa00922706 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 @@ -5,7 +5,7 @@ - + WinExe diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 8cfe5d8af2..faa5d9e6fc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -56,7 +56,6 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - RulesetID = i % 4, // workaround for efcore 5 compatibility. OnlineBeatmapID = beatmapId, Length = length, BPM = bpm, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 9b8b74e6f6..53a956c77c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -186,7 +186,6 @@ namespace osu.Game.Tests.Visual.SongSelect Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. StarDifficulty = difficultyIndex + 1, Version = $"SR{difficultyIndex + 1}" }).ToList() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 057b539e44..5731b1ac2c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -911,11 +911,9 @@ namespace osu.Game.Tests.Visual.SongSelect int length = RNG.Next(30000, 200000); double bpm = RNG.NextSingle(80, 200); - var ruleset = getRuleset(); beatmaps.Add(new BeatmapInfo { - Ruleset = ruleset, - RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility. + Ruleset = getRuleset(), OnlineBeatmapID = beatmapId, Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 6f8e0fac6f..e36b3cdc74 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 115d1b33bb..b4ea898b7d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,8 +171,6 @@ namespace osu.Game.Beatmaps if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); - beatmapSet.Requery(ContextFactory); - // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 64428882ac..d809dbcb01 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -462,8 +462,6 @@ namespace osu.Game.Database // Dereference the existing file info, since the file model will be removed. if (file.FileInfo != null) { - file.Requery(usage.Context); - Files.Dereference(file.FileInfo); // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked @@ -637,12 +635,10 @@ namespace osu.Game.Database { using (Stream s = reader.GetStream(file)) { - var fileInfo = files.Add(s); fileInfos.Add(new TFileModel { Filename = file.Substring(prefix.Length).ToStandardisedPath(), - FileInfo = fileInfo, - FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility. + FileInfo = files.Add(s) }); } } diff --git a/osu.Game/Database/DatabaseWorkaroundExtensions.cs b/osu.Game/Database/DatabaseWorkaroundExtensions.cs deleted file mode 100644 index a3a982f232..0000000000 --- a/osu.Game/Database/DatabaseWorkaroundExtensions.cs +++ /dev/null @@ -1,71 +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 osu.Game.Beatmaps; -using osu.Game.Scoring; -using osu.Game.Skinning; - -namespace osu.Game.Database -{ - /// - /// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety. - /// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent. - /// - public static class DatabaseWorkaroundExtensions - { - /// - /// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type. - /// - /// - /// - public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory) - { - switch (model) - { - case SkinInfo skinInfo: - requeryFiles(skinInfo.Files, contextFactory); - break; - - case ScoreInfo scoreInfo: - requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory); - requeryFiles(scoreInfo.Files, contextFactory); - break; - - case BeatmapSetInfo beatmapSetInfo: - var context = contextFactory.Get(); - - foreach (var beatmap in beatmapSetInfo.Beatmaps) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID); - } - - requeryFiles(beatmapSetInfo.Files, contextFactory); - break; - - default: - throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model)); - } - - void requeryFiles(List files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo - { - var dbContext = databaseContextFactory.Get(); - - foreach (var file in files) - { - Requery(file, dbContext); - } - } - } - - public static void Requery(this INamedFileInfo file, OsuDbContext dbContext) - { - // Workaround System.InvalidOperationException - // The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. - file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID); - } - } -} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2342ab07d4..2aae62edea 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -110,10 +111,10 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder - .UseSqlite(connectionString, - sqliteOptions => sqliteOptions - .CommandTimeout(10) - .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery)) + // this is required for the time being due to the way we are querying in places like BeatmapStore. + // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. + .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) + .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 78101991f6..f5192f3a40 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -73,7 +73,7 @@ namespace osu.Game.Scoring } set { - modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym })); + modsJson = null; mods = value; } } @@ -86,7 +86,16 @@ namespace osu.Game.Scoring [Column("Mods")] public string ModsJson { - get => modsJson; + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym })); + } set { modsJson = value; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7d0abc5996..c7ee26c248 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -53,11 +53,6 @@ namespace osu.Game.Scoring this.configManager = configManager; } - protected override void PreImport(ScoreInfo model) - { - model.Requery(ContextFactory); - } - protected override ScoreInfo CreateModel(ArchiveReader archive) { if (archive == null) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 894a068b7f..9257636301 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -142,11 +142,6 @@ namespace osu.Game.Skinning } } - protected override void PreImport(SkinInfo model) - { - model.Requery(ContextFactory); - } - /// /// Retrieve a instance for the provided /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 360c522193..9731c1d5ea 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,10 +24,10 @@ - - + + - + diff --git a/osu.iOS.props b/osu.iOS.props index b763a91dfb..11677d345e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -90,6 +90,8 @@ + + From c4f3714385c411dca8092f3163aad2817ff7f62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 18:39:52 +0100 Subject: [PATCH 061/120] Make hold note input tests fail due to head hiding --- .../TestSceneHoldNoteInput.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 596430f9e5..7ae69bf7d7 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects; @@ -345,6 +347,14 @@ namespace osu.Game.Rulesets.Mania.Tests AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + + AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head); + AddAssert("head is visible", + () => currentPlayer.ChildrenOfType() + .Single(note => note.HitObject == beatmap.HitObjects[0]) + .Head + .Alpha == 1); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } @@ -352,6 +362,8 @@ namespace osu.Game.Rulesets.Mania.Tests { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + protected override bool PauseOnFocusLost => false; public ScoreAccessibleReplayPlayer(Score score) From 9a330c4c56aeeda049cfccdd6a15f10c1966758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Mar 2021 18:34:31 +0100 Subject: [PATCH 062/120] Fix mania hold note heads hiding when frozen This was an insidious regression from a3dc1d5. Prior to that commit, `DrawableHoldNoteHead` had `UpdateStateTransforms()` overridden, to set the hold note head's lifetime. When that method was split into `UpdateInitialStateTransforms()` and `UpdateHitStateTransforms()`, the lifetime set was moved to the former. Unfortunately, that override served two purposes: both to set the lifetime, and to suppress hit animations which would normally be added by the base `DrawableManiaHitObject`. That fact being missed led to `UpdateHitStateTransforms()` hiding the hold note head immediately on hit and with a slight delay on miss. To resolve, explicitly override `UpdateHitStateTransforms()` and suppress the base call, with an explanatory comment. --- .../Objects/Drawables/DrawableHoldNoteHead.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index 75dcf0e55e..35ba2465fa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -1,6 +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 osu.Game.Rulesets.Objects.Drawables; + namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -25,6 +27,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables LifetimeEnd = LifetimeStart + 30000; } + protected override void UpdateHitStateTransforms(ArmedState state) + { + // suppress the base call explicitly. + // the hold note head should never change its visual state on its own due to the "freezing" mechanic + // (when hit, it remains visible in place at the judgement line; when dropped, it will scroll past the line). + // it will be hidden along with its parenting hold note when required. + } + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note public override void OnReleased(ManiaAction action) From e31d583a7f468853844bb1e8aa38f2d70225f122 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 21 Mar 2021 11:16:59 -0700 Subject: [PATCH 063/120] Add comments count to user profile overlay --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 6 ++++++ osu.Game/Users/User.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 662f55317b..e73579fad0 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -115,6 +116,11 @@ namespace osu.Game.Overlays.Profile.Header topLinkContainer.AddText("Contributed "); topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); + addSpacer(topLinkContainer); + + topLinkContainer.AddText("Posted "); + topLinkContainer.AddLink("comment".ToQuantity(user.CommentsCount, "#,##0"), $"{api.WebsiteRootUrl}/comments?user_id={user.Id}", creationParameters: embolden); + string websiteWithoutProtocol = user.Website; if (!string.IsNullOrEmpty(websiteWithoutProtocol)) diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 6c45417db0..74ffb7c457 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -120,6 +120,9 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; + [JsonProperty(@"comments_count")] + public int CommentsCount; + [JsonProperty(@"follower_count")] public int FollowerCount; From 9bc6cdf042317265f7e37b3feaf58f10770d6fac Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 21 Mar 2021 11:19:07 -0700 Subject: [PATCH 064/120] Fix singular format regression on forum post text --- osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index e73579fad0..fe61e532e1 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Profile.Header } topLinkContainer.AddText("Contributed "); - topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); + topLinkContainer.AddLink("forum post".ToQuantity(user.PostCount, "#,##0"), $"{api.WebsiteRootUrl}/users/{user.Id}/posts", creationParameters: embolden); addSpacer(topLinkContainer); From f7bf23dbe9b51fa6f2a8168efdf4ce1b081e8874 Mon Sep 17 00:00:00 2001 From: owen-young Date: Sun, 21 Mar 2021 21:50:19 -0500 Subject: [PATCH 065/120] first attempt at changing windowMode to be fullscreen on default --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dd775888a1..2fd6331c86 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,6 +126,8 @@ namespace osu.Game private Bindable configSkin; + private Bindable windowMode; + private readonly string[] args; private readonly List overlays = new List(); @@ -631,6 +633,12 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode); + windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => + { + windowMode.Value = WindowMode.Windowed; + }), true); + var onScreenDisplay = new OnScreenDisplay(); onScreenDisplay.BeginTracking(this, frameworkConfig); From 073dba5330663073433b38798626ae0d6781d971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 14:05:37 +0900 Subject: [PATCH 066/120] Remove local workarounds to attempt to avoid crashes on skin change --- osu.Game/Skinning/LegacySkin.cs | 76 +-------------------------------- 1 file changed, 1 insertion(+), 75 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ec49d43c67..12abc4d867 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.IO; using System.Linq; using JetBrains.Annotations; -using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -462,7 +461,7 @@ namespace osu.Game.Skinning var sample = Samples?.Get(lookup); if (sample != null) - return new LegacySkinSample(sample, this); + return sample; } return null; @@ -505,78 +504,5 @@ namespace osu.Game.Skinning Textures?.Dispose(); Samples?.Dispose(); } - - /// - /// A sample wrapper which keeps a reference to the contained skin to avoid finalizer garbage collection of the managing SampleStore. - /// - private class LegacySkinSample : ISample, IDisposable - { - private readonly Sample sample; - - [UsedImplicitly] - private readonly LegacySkin skin; - - public LegacySkinSample(Sample sample, LegacySkin skin) - { - this.sample = sample; - this.skin = skin; - } - - public SampleChannel Play() - { - return sample.Play(); - } - - public SampleChannel GetChannel() - { - return sample.GetChannel(); - } - - public double Length => sample.Length; - - public Bindable PlaybackConcurrency => sample.PlaybackConcurrency; - public BindableNumber Volume => sample.Volume; - - public BindableNumber Balance => sample.Balance; - - public BindableNumber Frequency => sample.Frequency; - - public BindableNumber Tempo => sample.Tempo; - - public void BindAdjustments(IAggregateAudioAdjustment component) - { - sample.BindAdjustments(component); - } - - public void UnbindAdjustments(IAggregateAudioAdjustment component) - { - sample.UnbindAdjustments(component); - } - - public void AddAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - sample.AddAdjustment(type, adjustBindable); - } - - public void RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - sample.RemoveAdjustment(type, adjustBindable); - } - - public void RemoveAllAdjustments(AdjustableProperty type) - { - sample.RemoveAllAdjustments(type); - } - - public IBindable AggregateVolume => sample.AggregateVolume; - - public IBindable AggregateBalance => sample.AggregateBalance; - - public IBindable AggregateFrequency => sample.AggregateFrequency; - - public IBindable AggregateTempo => sample.AggregateTempo; - - public void Dispose() => sample.Dispose(); - } } } From db64fac8241710308f02399909ec7f6f05be6251 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Mar 2021 15:26:22 +0900 Subject: [PATCH 067/120] Delay key fade in legacy mania skins --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 78ccb83a8c..174324f5f6 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { if (action == column.Action.Value) { - upSprite.FadeTo(1); - downSprite.FadeTo(0); + upSprite.Delay(80).FadeTo(1); + downSprite.Delay(80).FadeTo(0); } } } From fc632fd48aae56b1045a1955b035d8ac574609d8 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 01:30:20 -0500 Subject: [PATCH 068/120] Added WindowSetting setting to OsuSetting enum so that it can be set by default at startup. Modified LayoutSettings.cs so that when it is changed in the settings, it is written to the local settings as well. --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ osu.Game/OsuGame.cs | 9 +++------ .../Settings/Sections/Graphics/LayoutSettings.cs | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d0fa45bb7a..cd74fe25f4 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,6 +91,8 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); + Set(OsuSetting.WindowSetting, WindowMode.Fullscreen); + // Gameplay Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -233,6 +235,7 @@ namespace osu.Game.Configuration MenuVoice, CursorRotation, MenuParallax, + WindowSetting, BeatmapDetailTab, BeatmapDetailModsFilter, Username, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2fd6331c86..84737a56e4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -234,6 +234,9 @@ namespace osu.Game SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); + + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; } private ExternalLinkOpener externalLinkOpener; @@ -633,12 +636,6 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode); - windowMode.BindValueChanged(mode => ScheduleAfterChildren(() => - { - windowMode.Value = WindowMode.Windowed; - }), true); - var onScreenDisplay = new OnScreenDisplay(); onScreenDisplay.BeginTracking(this, frameworkConfig); diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 4d5c2e06eb..ab662cb9a0 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable scalingMode; private Bindable sizeFullscreen; + private Bindable windowMode; private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) }); @@ -56,6 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); + windowMode = osuConfig.GetBindable(OsuSetting.WindowSetting); if (host.Window != null) { @@ -141,7 +143,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown(); + windowModeDropdown.Current.ValueChanged += mode => { + windowMode.Value = mode.NewValue; + updateResolutionDropdown(); + }; windowModes.BindCollectionChanged((sender, args) => { From c4d08463addcf532926308dc319eebb981d530a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:04:51 +0900 Subject: [PATCH 069/120] Fix spinners playing looping sound too long in the editor The `OnComplete` event was never being run due to the transform playing out longer than the spinner's lifetime. I've matched the durations, but also moved the `Stop()` call to what I deem a safer place to run it (I did notice that without this it would still potentially never fire). Note that this is more noticeable in the editor because of lifetime extension. In gameplay, the returning of a spinner to the pool will clean things up (but in the editor that can take longer, depending on timeline zoom level). Another thing worth mentioning is that the fade doesn't actually work. This is due to https://github.com/ppy/osu-framework/pull/4212. Closes #12119. --- .../Objects/Drawables/DrawableSpinner.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3d614c2dbd..d92f63eb89 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -39,6 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Bindable isSpinning; private bool spinnerFrequencyModulate; + private const double fade_out_duration = 160; + public DrawableSpinner() : this(null) { @@ -136,7 +138,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } else { - spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop()); + if (spinningSample != null) + spinningSample.Volume.Value = 0; + + spinningSample?.VolumeTo(0, fade_out_duration); } } @@ -173,7 +178,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateHitStateTransforms(state); - this.FadeOut(160).Expire(); + this.FadeOut(fade_out_duration).OnComplete(_ => + { + // looping sample should be stopped here as it is safer than running in the OnComplete + // of the volume transition above. + spinningSample.Stop(); + }); + + Expire(); // skin change does a rewind of transforms, which will stop the spinning sound from playing if it's currently in playback. isSpinning?.TriggerChange(); From 690fb9224aaee2d2d4930de181992eb53970b32d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:18:31 +0900 Subject: [PATCH 070/120] Combine constants for readability --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs | 4 +++- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs index 73aece1ed4..9ec122a12c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion { + public const double FADE_IN_DURATION = 80; + private readonly IBindable direction = new Bindable(); private Drawable explosion; @@ -72,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy (explosion as IFramedAnimation)?.GotoFrame(0); - explosion?.FadeInFromZero(80) + explosion?.FadeInFromZero(fade_in_duration) .Then().FadeOut(120); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs index 174324f5f6..10319a7d4d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyKeyArea.cs @@ -101,8 +101,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { if (action == column.Action.Value) { - upSprite.Delay(80).FadeTo(1); - downSprite.Delay(80).FadeTo(0); + upSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(1); + downSprite.Delay(LegacyHitExplosion.FADE_IN_DURATION).FadeTo(0); } } } From 5b1d9f4cf07abd14860bc9ecf887fa98e55b4dc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Mar 2021 16:19:29 +0900 Subject: [PATCH 071/120] Fix constant case --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs index 9ec122a12c..e4d466dca5 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyHitExplosion.cs @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy (explosion as IFramedAnimation)?.GotoFrame(0); - explosion?.FadeInFromZero(fade_in_duration) + explosion?.FadeInFromZero(FADE_IN_DURATION) .Then().FadeOut(120); } } From e60ff45b73a2e236eed250f869a3b4afbafd3292 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Mar 2021 16:57:40 +0900 Subject: [PATCH 072/120] Add another test for colinear perfect curves --- .../colinear-perfect-curve-expected-conversion.json | 13 +++++++++++++ .../Testing/Beatmaps/colinear-perfect-curve.osu | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json index 96e4bf1637..1a0bd66246 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json @@ -1,5 +1,18 @@ { "Mappings": [{ + "StartTime": 114993, + "Objects": [{ + "StartTime": 114993, + "EndTime": 114993, + "X": 493, + "Y": 92 + }, { + "StartTime": 115290, + "EndTime": 115290, + "X": 451.659241, + "Y": 267.188 + }] + }, { "StartTime": 118858.0, "Objects": [{ "StartTime": 118858.0, diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu index 8c3edc9571..dd35098502 100644 --- a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu @@ -9,7 +9,9 @@ SliderMultiplier:1.87 SliderTickRate:1 [TimingPoints] -49051,230.769230769231,4,2,1,15,1,0 +114000,346.820809248555,4,2,1,71,1,0 +118000,230.769230769231,4,2,1,15,1,0 [HitObjects] +493,92,114993,2,0,P|472:181|442:308,1,180,12|0,0:0|0:0,0:0:0:0: 219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0: From a65e491768441017e9cfe7e2e4fec2d15e57d708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 20:00:36 +0100 Subject: [PATCH 073/120] Remove osuTK desktop rider run config No longer operational since 6eadae8. --- .../runConfigurations/osu___legacy_osuTK_.xml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml deleted file mode 100644 index 9ece926b34..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___legacy_osuTK_.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - \ No newline at end of file From d85929d721254391d5e7e23d2e81d56180ee07f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 22:45:18 +0100 Subject: [PATCH 074/120] Adjust autoplay generation tests to match expected behaviour --- .../TestSceneAutoGeneration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index a5248c7712..399a46aa77 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release 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"); } @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); @@ -148,9 +148,9 @@ namespace osu.Game.Rulesets.Mania.Tests Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames"); Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time"); - Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); + Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time"); Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time"); - Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); + Assert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time"); Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released"); @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Tests // | | | var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); - beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); + beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); var generated = new ManiaAutoGenerator(beatmap).Generate(); From 29d4162e4e2110973571a8fcfe83509e3586d50a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 22 Mar 2021 22:38:51 +0100 Subject: [PATCH 075/120] Remove release delay for hold notes when generating autoplay It was more intended for normal notes anyway (as they would be released pretty much instantaneously, if it weren't for the delay). --- .../Replays/ManiaAutoGenerator.cs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 3ebbe5af8e..7c51d58b74 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Replays; @@ -85,20 +86,28 @@ namespace osu.Game.Rulesets.Mania.Replays { var currentObject = Beatmap.HitObjects[i]; var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button - - double endTime = currentObject.GetEndTime(); - - bool canDelayKeyUp = nextObjectInColumn == null || - nextObjectInColumn.StartTime > endTime + RELEASE_DELAY; - - double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9; + var releaseTime = calculateReleaseTime(currentObject, nextObjectInColumn); yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column }; - yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column }; + yield return new ReleasePoint { Time = releaseTime, Column = currentObject.Column }; } } + private double calculateReleaseTime(HitObject currentObject, HitObject nextObject) + { + double endTime = currentObject.GetEndTime(); + + if (currentObject is HoldNote) + // hold note releases must be timed exactly. + return endTime; + + bool canDelayKeyUpFully = nextObject == null || + nextObject.StartTime > endTime + RELEASE_DELAY; + + return endTime + (canDelayKeyUpFully ? RELEASE_DELAY : (nextObject.StartTime - endTime) * 0.9); + } + protected override HitObject GetNextObject(int currentIndex) { int desiredColumn = Beatmap.HitObjects[currentIndex].Column; From 8ea7271d5c96b062bebcb8b702da739d9ef4dac1 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 19:48:52 -0500 Subject: [PATCH 076/120] moved windowmode code to LoadComplete (?) --- 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 84737a56e4..ff215b63e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -234,9 +234,6 @@ namespace osu.Game SelectedMods.BindValueChanged(modsChanged); Beatmap.BindValueChanged(beatmapChanged, true); - - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; } private ExternalLinkOpener externalLinkOpener; @@ -576,6 +573,9 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; + AddRange(new Drawable[] { new VolumeControlReceptor From bdcb9451f79798197825106a0762a0c823b771c7 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 20:17:05 -0500 Subject: [PATCH 077/120] added code to OsuGameBase to default to fullscreen, but that might not be a good place to put it. --- osu.Game/OsuGameBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e1c7b67a8c..bcd384604f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -21,6 +21,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; +using osu.Framework.Configuration; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; @@ -119,6 +120,7 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; + private Bindable windowMode; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -361,6 +363,9 @@ namespace osu.Game fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); + windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + windowMode.Value = WindowMode.Fullscreen; + FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } From 098005393e999f5623769adc9e5458bb86e37513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 10:38:37 +0900 Subject: [PATCH 078/120] Remove unnecessary null checks and debug code --- .../Objects/Drawables/DrawableSpinner.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d92f63eb89..32a0a14dc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -133,15 +133,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (tracking.NewValue) { if (!spinningSample.IsPlaying) - spinningSample?.Play(); - spinningSample?.VolumeTo(1, 300); + spinningSample.Play(); + + spinningSample.VolumeTo(1, 300); } else { - if (spinningSample != null) - spinningSample.Volume.Value = 0; - - spinningSample?.VolumeTo(0, fade_out_duration); + spinningSample.VolumeTo(0, fade_out_duration); } } From 16b3f22caf2754d97aacbf40f0d429e8b3cecdce Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 22 Mar 2021 19:32:17 -0700 Subject: [PATCH 079/120] Fix incorrect trash icon being used on deleted comments counter --- osu.Game/Overlays/Comments/DeletedCommentsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs index 56588ef0a8..8c40d79f7a 100644 --- a/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs +++ b/osu.Game/Overlays/Comments/DeletedCommentsCounter.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Comments { new SpriteIcon { - Icon = FontAwesome.Solid.Trash, + Icon = FontAwesome.Regular.TrashAlt, Size = new Vector2(14), }, countText = new OsuSpriteText From 9f788f58548d629144c4b8946b74542b424ea7a4 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 22:52:16 -0500 Subject: [PATCH 080/120] removed code from OsuGameBase for fullscreen.....OsuSetting still exists but cannot figure out a way to set it to a default and have it actually work --- osu.Game/OsuGame.cs | 6 +++--- osu.Game/OsuGameBase.cs | 4 ---- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 6 ++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ff215b63e5..2f2428e781 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,7 +126,7 @@ namespace osu.Game private Bindable configSkin; - private Bindable windowMode; + // private Bindable windowMode; private readonly string[] args; @@ -573,8 +573,8 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; + // windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); + // frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; AddRange(new Drawable[] { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index bcd384604f..8b1fe20708 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -120,7 +120,6 @@ namespace osu.Game protected Bindable Beatmap { get; private set; } // cached via load() method private Bindable fpsDisplayVisible; - private Bindable windowMode; public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); @@ -363,9 +362,6 @@ namespace osu.Game fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); - windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - windowMode.Value = WindowMode.Fullscreen; - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index ab662cb9a0..a0cb8fc2de 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -57,7 +58,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); - windowMode = osuConfig.GetBindable(OsuSetting.WindowSetting); + windowMode = config.GetBindable(FrameworkSetting.WindowMode); + Logger.Log($"windowMode {windowMode.Value}"); if (host.Window != null) { @@ -71,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "Screen mode", ItemSource = windowModes, - Current = config.GetBindable(FrameworkSetting.WindowMode), + Current = config.GetBindable(FrameworkSetting.WindowMode) }, resolutionDropdown = new ResolutionSettingsDropdown { From d9e2c44a34c17d9ed0f93587460fd506480ad478 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Mon, 22 Mar 2021 23:36:55 -0500 Subject: [PATCH 081/120] implemented GetFrameworkConfigDefaults for overriding framework default, removed previous code that added a new OsuSetting and modified settings layout. --- osu.Game/Configuration/OsuConfigManager.cs | 3 --- osu.Game/OsuGame.cs | 11 ++++++----- osu.Game/OsuGameBase.cs | 1 - .../Settings/Sections/Graphics/LayoutSettings.cs | 11 ++--------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index cd74fe25f4..d0fa45bb7a 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,8 +91,6 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); - Set(OsuSetting.WindowSetting, WindowMode.Fullscreen); - // Gameplay Set(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -235,7 +233,6 @@ namespace osu.Game.Configuration MenuVoice, CursorRotation, MenuParallax, - WindowSetting, BeatmapDetailTab, BeatmapDetailModsFilter, Username, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 2f2428e781..ffb694c27e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,8 +126,6 @@ namespace osu.Game private Bindable configSkin; - // private Bindable windowMode; - private readonly string[] args; private readonly List overlays = new List(); @@ -573,9 +571,6 @@ namespace osu.Game dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); - // windowMode = LocalConfig.GetBindable(OsuSetting.WindowSetting); - // frameworkConfig.GetBindable(FrameworkSetting.WindowMode).Value = windowMode.Value; - AddRange(new Drawable[] { new VolumeControlReceptor @@ -1012,5 +1007,11 @@ namespace osu.Game if (newScreen == null) Exit(); } + + protected override IDictionary GetFrameworkConfigDefaults() { + IDictionary defaultOverrides = new Dictionary(); + defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); + return defaultOverrides; + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8b1fe20708..e1c7b67a8c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -21,7 +21,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; -using osu.Framework.Configuration; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index a0cb8fc2de..4d5c2e06eb 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -32,7 +31,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable scalingMode; private Bindable sizeFullscreen; - private Bindable windowMode; private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) }); @@ -58,8 +56,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); - windowMode = config.GetBindable(FrameworkSetting.WindowMode); - Logger.Log($"windowMode {windowMode.Value}"); if (host.Window != null) { @@ -73,7 +69,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "Screen mode", ItemSource = windowModes, - Current = config.GetBindable(FrameworkSetting.WindowMode) + Current = config.GetBindable(FrameworkSetting.WindowMode), }, resolutionDropdown = new ResolutionSettingsDropdown { @@ -145,10 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics scalingSettings.ForEach(s => bindPreviewEvent(s.Current)); - windowModeDropdown.Current.ValueChanged += mode => { - windowMode.Value = mode.NewValue; - updateResolutionDropdown(); - }; + windowModeDropdown.Current.ValueChanged += _ => updateResolutionDropdown(); windowModes.BindCollectionChanged((sender, args) => { From 58c60100b431e6ce6ee720ff72206e9f9071d070 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:04:47 +0900 Subject: [PATCH 082/120] Fix APIScoreToken's data type not matching server side --- osu.Game/Online/Rooms/APIScoreToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/APIScoreToken.cs b/osu.Game/Online/Rooms/APIScoreToken.cs index f652c1720d..6b559876de 100644 --- a/osu.Game/Online/Rooms/APIScoreToken.cs +++ b/osu.Game/Online/Rooms/APIScoreToken.cs @@ -8,6 +8,6 @@ namespace osu.Game.Online.Rooms public class APIScoreToken { [JsonProperty("id")] - public int ID { get; set; } + public long ID { get; set; } } } From 9c690f9545959c117b4358e391333b3882abb34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:08:00 +0900 Subject: [PATCH 083/120] Fix second usage --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index a75e4bdc07..2b6dbd9dcb 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected readonly PlaylistItem PlaylistItem; - protected int? Token { get; private set; } + protected long? Token { get; private set; } [Resolved] private IAPIProvider api { get; set; } From 254b0f5dc3b2469fdccf6baf0231af30baf11c9b Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 00:24:33 -0500 Subject: [PATCH 084/120] removed line (?) - tried doing testing to see if it launched in fullscreen (i.e., overriding the method ppy mentioned), but to no avail :( --- osu.Game/OsuGame.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ffb694c27e..a52899433a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1007,7 +1007,6 @@ namespace osu.Game if (newScreen == null) Exit(); } - protected override IDictionary GetFrameworkConfigDefaults() { IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); From 1171214541361b72ae23405a3549fd918af16fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:51:22 +0900 Subject: [PATCH 085/120] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e0392bd687..75ac298626 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9731c1d5ea..b90c938a8b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 11677d345e..ce182a3054 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 08fcdc8ee46748eee4c8d66d5842b57f16d9d896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:38:00 +0900 Subject: [PATCH 086/120] Update difficulty calculator tests with floating point differences --- .../OsuDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index a365ea10d4..c2119585ab 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.9311451172608853d, "diffcalc-test")] - [TestCase(1.0736587013228804d, "zero-length-sliders")] + [TestCase(6.9311451172574934d, "diffcalc-test")] + [TestCase(1.0736586907780401d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.6228371119393064d, "diffcalc-test")] - [TestCase(1.2864585434597433d, "zero-length-sliders")] + [TestCase(8.6228371119271454d, "diffcalc-test")] + [TestCase(1.2864585280364178d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); From f5ba746ae5522e6c1ba34f6a34218491ff4aa626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:31:28 +0900 Subject: [PATCH 087/120] Fail all API requests sent to DummyAPIAccess Until now, API requests sent to dummy API were just lost in the void. In most cases this somehow worked as expected, but any logic which is waiting on a request to finish will potentially never get a response. Going forward, I'm not 100% sure that every `Wait` on a web response will have local timeout logic (I think there is a certain amount of assumption that this is being managed for us by `APIAccess`), so I've made this change to better handle such cases going forward. Now, rather than nothing happening, requests will trigger a failure via the existing exception logic rather than silently pretending the request never arrived. --- osu.Game/Online/API/APIRequest.cs | 8 ++++++-- osu.Game/Online/API/DummyAPIAccess.cs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index a7174324d8..16d1b3ab17 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -181,9 +181,13 @@ namespace osu.Game.Online.API /// Whether we are in a failed or cancelled state. private bool checkAndScheduleFailure() { - if (API == null || pendingFailure == null) return cancelled; + if (pendingFailure == null) return cancelled; + + if (API == null) + pendingFailure(); + else + API.Schedule(pendingFailure); - API.Schedule(pendingFailure); pendingFailure = null; return true; } diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 943b52db88..3cb22381c1 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -55,7 +55,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - HandleRequest?.Invoke(request); + if (HandleRequest != null) + HandleRequest.Invoke(request); + else + // this will fail due to not receiving an APIAccess, and trigger a failure on the request. + // this is intended - any request in testing that needs non-failures should use HandleRequest. + request.Perform(this); } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); From ce452565f45bba3106ffe89a21c63d4662e8a40e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 17:50:31 +0900 Subject: [PATCH 088/120] Avoid firing any kind of failures after success --- osu.Game/Online/API/APIRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 16d1b3ab17..1a6868cfa4 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -131,8 +131,11 @@ namespace osu.Game.Online.API { } + private bool succeeded; + internal virtual void TriggerSuccess() { + succeeded = true; Success?.Invoke(); } @@ -145,10 +148,7 @@ namespace osu.Game.Online.API public void Fail(Exception e) { - if (WebRequest?.Completed == true) - return; - - if (cancelled) + if (succeeded || cancelled) return; cancelled = true; From aeff9bd8531962e7d509623407a819f3c7f69ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 18:08:32 +0900 Subject: [PATCH 089/120] Add return bool to HandleRequest to better trigger failures --- .../Online/TestDummyAPIRequestHandling.cs | 8 ++++++-- .../TestSceneSeasonalBackgroundLoader.cs | 4 +++- .../Online/TestSceneBeatmapListingOverlay.cs | 13 ++++++------ .../Online/TestSceneChangelogOverlay.cs | 6 ++++-- .../Visual/Online/TestSceneChatOverlay.cs | 20 +++++++++++++++++++ .../Online/TestSceneCommentsContainer.cs | 3 ++- .../Visual/Online/TestSceneNewsOverlay.cs | 3 ++- .../TestScenePlaylistsResultsScreen.cs | 13 ++++++++++++ .../TestSceneBeatmapRecommendations.cs | 4 +++- osu.Game/Online/API/DummyAPIAccess.cs | 9 +++++---- .../Multiplayer/TestMultiplayerRoomManager.cs | 18 +++++++++-------- 11 files changed, 75 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 42948c3731..aa29d76843 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -23,8 +23,10 @@ namespace osu.Game.Tests.Online { case CommentVoteRequest cRequest: cRequest.TriggerSuccess(new CommentBundle()); - break; + return true; } + + return false; }); CommentVoteRequest request = null; @@ -108,8 +110,10 @@ namespace osu.Game.Tests.Online { case LeaveChannelRequest cRequest: cRequest.TriggerSuccess(); - break; + return true; } + + return false; }); } } diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index e7cf830db0..dc5a4f4a3e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -135,13 +135,15 @@ namespace osu.Game.Tests.Visual.Background dummyAPI.HandleRequest = request => { if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest)) - return; + return false; backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds { Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(), EndDate = endDate }); + + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index 1349264bf9..156d6b744e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -30,13 +30,14 @@ namespace osu.Game.Tests.Visual.Online ((DummyAPIAccess)API).HandleRequest = req => { - if (req is SearchBeatmapSetsRequest searchBeatmapSetsRequest) + if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; + + searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse { - searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse - { - BeatmapSets = setsForResponse, - }); - } + BeatmapSets = setsForResponse, + }); + + return true; }; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index cd2c4e9346..8818ac75b1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -63,13 +63,15 @@ namespace osu.Game.Tests.Visual.Online Builds = builds.Values.ToList() }; changelogRequest.TriggerSuccess(changelogResponse); - break; + return true; case GetChangelogBuildRequest buildRequest: if (requestedBuild != null) buildRequest.TriggerSuccess(requestedBuild); - break; + return true; } + + return false; }; Child = changelog = new TestChangelogOverlay(); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index fca642ad6c..b13dd34ebc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.Selection; @@ -64,6 +66,24 @@ namespace osu.Game.Tests.Visual.Online }); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => + { + ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case JoinChannelRequest _: + return true; + } + + return false; + }; + }); + } + [Test] public void TestHideOverlay() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index c2a18330c9..cd22bb2513 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -85,9 +85,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetCommentsRequest getCommentsRequest)) - return; + return false; getCommentsRequest.TriggerSuccess(commentBundle); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs index 37d51c16d2..6ebe8fcc07 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs @@ -33,9 +33,10 @@ namespace osu.Game.Tests.Visual.Online dummyAPI.HandleRequest = request => { if (!(request is GetNewsRequest getNewsRequest)) - return; + return false; getNewsRequest.TriggerSuccess(r); + return true; }; }); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index be8032cde8..61d49e4018 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -170,6 +170,17 @@ namespace osu.Game.Tests.Visual.Playlists private void bindHandler(bool delayed = false, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => { + // pre-check for requests we should be handling (as they are scheduled below). + switch (request) + { + case ShowPlaylistUserScoreRequest _: + case IndexPlaylistScoresRequest _: + break; + + default: + return false; + } + requestComplete = false; double delay = delayed ? 3000 : 0; @@ -196,6 +207,8 @@ namespace osu.Game.Tests.Visual.Playlists break; } }, delay); + + return true; }; private void triggerSuccess(APIRequest req, T result) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53a956c77c..5e2d5eba5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -32,8 +32,10 @@ namespace osu.Game.Tests.Visual.SongSelect { case GetUserRequest userRequest: userRequest.TriggerSuccess(getUser(userRequest.Ruleset.ID)); - break; + return true; } + + return false; }; }); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 3cb22381c1..52f2365165 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -34,8 +34,9 @@ namespace osu.Game.Online.API /// /// Provide handling logic for an arbitrary API request. + /// Should return true is a request was handled. If null or false return, the request will be failed with a . /// - public Action HandleRequest; + public Func HandleRequest; private readonly Bindable state = new Bindable(APIState.Online); @@ -55,12 +56,12 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - if (HandleRequest != null) - HandleRequest.Invoke(request); - else + if (HandleRequest?.Invoke(request) != true) + { // this will fail due to not receiving an APIAccess, and trigger a failure on the request. // this is intended - any request in testing that needs non-failures should use HandleRequest. request.Perform(this); + } } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs index 7e824c4d7c..315be510a3 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs @@ -52,15 +52,15 @@ namespace osu.Game.Tests.Visual.Multiplayer Rooms.Add(createdRoom); createRoomRequest.TriggerSuccess(createdRoom); - break; + return true; case JoinRoomRequest joinRoomRequest: joinRoomRequest.TriggerSuccess(); - break; + return true; case PartRoomRequest partRoomRequest: partRoomRequest.TriggerSuccess(); - break; + return true; case GetRoomsRequest getRoomsRequest: var roomsWithoutParticipants = new List(); @@ -76,11 +76,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } getRoomsRequest.TriggerSuccess(roomsWithoutParticipants); - break; + return true; case GetRoomRequest getRoomRequest: getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId)); - break; + return true; case GetBeatmapSetRequest getBeatmapSetRequest: var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type); @@ -89,11 +89,11 @@ namespace osu.Game.Tests.Visual.Multiplayer // Get the online API from the game's dependencies. game.Dependencies.Get().Queue(onlineReq); - break; + return true; case CreateRoomScoreRequest createRoomScoreRequest: createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); - break; + return true; case SubmitRoomScoreRequest submitRoomScoreRequest: submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore @@ -108,8 +108,10 @@ namespace osu.Game.Tests.Visual.Multiplayer User = api.LocalUser.Value, Statistics = new Dictionary() }); - break; + return true; } + + return false; }; } From 5267fb74c441c1c7f6ba48637f6981876ae5227e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:44:06 +0900 Subject: [PATCH 090/120] Add submission requests --- .../Online/Solo/CreateSoloScoreRequest.cs | 32 +++++++++++++ .../Online/Solo/SubmitSoloScoreRequest.cs | 45 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 osu.Game/Online/Solo/CreateSoloScoreRequest.cs create mode 100644 osu.Game/Online/Solo/SubmitSoloScoreRequest.cs diff --git a/osu.Game/Online/Solo/CreateSoloScoreRequest.cs b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs new file mode 100644 index 0000000000..ae5ac5e26c --- /dev/null +++ b/osu.Game/Online/Solo/CreateSoloScoreRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; + +namespace osu.Game.Online.Solo +{ + public class CreateSoloScoreRequest : APIRequest + { + private readonly int beatmapId; + private readonly string versionHash; + + public CreateSoloScoreRequest(int beatmapId, string versionHash) + { + this.beatmapId = beatmapId; + this.versionHash = versionHash; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + req.AddParameter("version_hash", versionHash); + return req; + } + + protected override string Target => $@"solo/{beatmapId}/scores"; + } +} diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs new file mode 100644 index 0000000000..98ba4fa052 --- /dev/null +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.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 System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Online.Solo +{ + public class SubmitSoloScoreRequest : APIRequest + { + private readonly long scoreId; + + private readonly int beatmapId; + + private readonly ScoreInfo scoreInfo; + + public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) + { + this.beatmapId = beatmapId; + this.scoreId = scoreId; + this.scoreInfo = scoreInfo; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.ContentType = "application/json"; + req.Method = HttpMethod.Put; + + req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + })); + + return req; + } + + protected override string Target => $@"solo/{beatmapId}/scores/{scoreId}"; + } +} From 6cb14e91c91043e1e3ddf0d2c999b9665def1d65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 14:47:15 +0900 Subject: [PATCH 091/120] Make Player abstract and introduce SoloPlayer --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 4 ++-- osu.Game/Screens/Play/SoloPlayer.cs | 9 +++++++++ osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/SoloPlayer.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 21d3bdaae3..2791952b66 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestPerformAtSongSelectFromPlayerLoader() { PushAndConfirm(() => new PlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new Player())); + PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) })); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Navigation public void TestPerformAtMenuFromPlayerLoader() { PushAndConfirm(() => new PlaySongSelect()); - PushAndConfirm(() => new PlayerLoader(() => new Player())); + PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer())); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0e221351aa..4cf0274614 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Play { [Cached] [Cached(typeof(ISamplePlaybackDisabler))] - public class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play /// /// Create a new player instance. /// - public Player(PlayerConfiguration configuration = null) + protected Player(PlayerConfiguration configuration = null) { Configuration = configuration ?? new PlayerConfiguration(); } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs new file mode 100644 index 0000000000..79860f3eda --- /dev/null +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -0,0 +1,9 @@ +// 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.Screens.Play +{ + public class SoloPlayer : Player + { + } +} diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index e61d5cce85..dfb4b59060 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Select SampleConfirm?.Play(); - this.Push(player = new PlayerLoader(() => new Player())); + this.Push(player = new PlayerLoader(() => new SoloPlayer())); return true; } From 7045fce55542b0f56a5907f0a87532bfef0728f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:00:02 +0900 Subject: [PATCH 092/120] Move score submission logic in general out to its own Player type --- .../Multiplayer/MultiplayerPlayer.cs | 1 - .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 74 ++----------- osu.Game/Screens/Play/SubmittingPlayer.cs | 100 ++++++++++++++++++ 3 files changed, 106 insertions(+), 69 deletions(-) create mode 100644 osu.Game/Screens/Play/SubmittingPlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index b3cd44d55a..ef34d40497 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -19,7 +19,6 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - // Todo: The "room" part of PlaylistsPlayer should be split out into an abstract player class to be inherited instead. public class MultiplayerPlayer : PlaylistsPlayer { protected override bool PauseOnFocusLost => false; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 2b6dbd9dcb..a6aff5e43f 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -4,11 +4,8 @@ using System; using System.Diagnostics; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -19,23 +16,12 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Playlists { - public class PlaylistsPlayer : Player + public class PlaylistsPlayer : RoomSubmittingPlayer { public Action Exited; - [Resolved(typeof(Room), nameof(Room.RoomID))] - protected Bindable RoomId { get; private set; } - protected readonly PlaylistItem PlaylistItem; - protected long? Token { get; private set; } - - [Resolved] - private IAPIProvider api { get; set; } - - [Resolved] - private IBindable ruleset { get; set; } - public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) : base(configuration) { @@ -43,12 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } [BackgroundDependencyLoader] - private void load() + private void load(IBindable ruleset) { - Token = null; - - bool failed = false; - // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineBeatmapID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); @@ -58,31 +40,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (!PlaylistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); - - var req = new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - req.Success += r => Token = r.ID; - req.Failure += e => - { - failed = true; - - if (string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token."); - else - Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); - - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); - }; - - api.Queue(req); - - while (!failed && !Token.HasValue) - Thread.Sleep(1000); } + protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + + public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + public override bool OnExiting(IScreen next) { if (base.OnExiting(next)) @@ -106,31 +69,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists return score; } - protected override async Task SubmitScore(Score score) - { - await base.SubmitScore(score).ConfigureAwait(false); - - Debug.Assert(Token != null); - - var tcs = new TaskCompletionSource(); - var request = new SubmitRoomScoreRequest(Token.Value, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); - - request.Success += s => - { - score.ScoreInfo.OnlineScoreID = s.ID; - tcs.SetResult(true); - }; - - request.Failure += e => - { - Logger.Error(e, "Failed to submit score"); - tcs.SetResult(false); - }; - - api.Queue(request); - await tcs.Task.ConfigureAwait(false); - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs new file mode 100644 index 0000000000..250e308b3c --- /dev/null +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -0,0 +1,100 @@ +// 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.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Logging; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + public abstract class RoomSubmittingPlayer : SubmittingPlayer + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + protected Bindable RoomId { get; private set; } + + protected RoomSubmittingPlayer(PlayerConfiguration configuration) + : base(configuration) + { + } + } + + public abstract class SubmittingPlayer : Player + { + protected long? Token { get; private set; } + + [Resolved] + private IAPIProvider api { get; set; } + + protected SubmittingPlayer(PlayerConfiguration configuration) + : base(configuration) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Token = null; + + bool failed = false; + + var req = CreateTokenRequestRequest(); + req.Success += r => Token = r.ID; + req.Failure += e => + { + failed = true; + + if (string.IsNullOrEmpty(e.Message)) + Logger.Error(e, "Failed to retrieve a score submission token."); + else + Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); + + Schedule(() => + { + ValidForResume = false; + this.Exit(); + }); + }; + + api.Queue(req); + + while (!failed && !Token.HasValue) + Thread.Sleep(1000); + } + + protected override async Task SubmitScore(Score score) + { + await base.SubmitScore(score).ConfigureAwait(false); + + Debug.Assert(Token != null); + + var tcs = new TaskCompletionSource(); + var request = CreateSubmissionRequest(score, Token.Value); + + request.Success += s => + { + score.ScoreInfo.OnlineScoreID = s.ID; + tcs.SetResult(true); + }; + + request.Failure += e => + { + Logger.Error(e, "Failed to submit score"); + tcs.SetResult(false); + }; + + api.Queue(request); + await tcs.Task.ConfigureAwait(false); + } + + protected abstract APIRequest CreateSubmissionRequest(Score score, long token); + + protected abstract APIRequest CreateTokenRequestRequest(); + } +} From 12f050264aa31af09089a32d23472defe67a0ec5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:33:31 +0900 Subject: [PATCH 093/120] Further split out a player class which submits to "rooms" --- .../Multiplayer/MultiplayerPlayer.cs | 3 +- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 10 +----- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 32 +++++++++++++++++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 15 ++------- 4 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Play/RoomSubmittingPlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index ef34d40497..2ba04b75d6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; @@ -19,7 +18,7 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public class MultiplayerPlayer : PlaylistsPlayer + public class MultiplayerPlayer : RoomSubmittingPlayer { protected override bool PauseOnFocusLost => false; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index a6aff5e43f..260d4961ff 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -7,7 +7,6 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Screens; -using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -20,12 +19,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public Action Exited; - protected readonly PlaylistItem PlaylistItem; - public PlaylistsPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) - : base(configuration) + : base(playlistItem, configuration) { - PlaylistItem = playlistItem; } [BackgroundDependencyLoader] @@ -42,10 +38,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); } - protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - - public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); - public override bool OnExiting(IScreen next) { if (base.OnExiting(next)) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs new file mode 100644 index 0000000000..c695e99874 --- /dev/null +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -0,0 +1,32 @@ +// 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.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Play +{ + /// + /// A player instance which submits to a room backing. This is generally used by playlists and multiplayer. + /// + public abstract class RoomSubmittingPlayer : SubmittingPlayer + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + protected Bindable RoomId { get; private set; } + + protected readonly PlaylistItem PlaylistItem; + + protected RoomSubmittingPlayer(PlaylistItem playlistItem, PlayerConfiguration configuration = null) + : base(configuration) + { + PlaylistItem = playlistItem; + } + + protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + + public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + } +} diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 250e308b3c..5577683f05 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Online.API; @@ -14,17 +13,9 @@ using osu.Game.Scoring; namespace osu.Game.Screens.Play { - public abstract class RoomSubmittingPlayer : SubmittingPlayer - { - [Resolved(typeof(Room), nameof(Room.RoomID))] - protected Bindable RoomId { get; private set; } - - protected RoomSubmittingPlayer(PlayerConfiguration configuration) - : base(configuration) - { - } - } - + /// + /// A player instance which supports submitting scores to an online store. + /// public abstract class SubmittingPlayer : Player { protected long? Token { get; private set; } From 194b2d05d3749b6b60796c699490df6320c62d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:35:06 +0900 Subject: [PATCH 094/120] Update SoloPlayer to derive SubmittingPlayer --- osu.Game/Screens/Play/SoloPlayer.cs | 15 ++++++++++++++- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 79860f3eda..f2f97c5d0d 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -1,9 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + namespace osu.Game.Screens.Play { - public class SoloPlayer : Player + public class SoloPlayer : SubmittingPlayer { + public override APIRequest CreateSubmissionRequest(Score score, int token) + { + throw new System.NotImplementedException(); + } + + protected override APIRequest CreateTokenRequestRequest() + { + throw new System.NotImplementedException(); + } } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5577683f05..e7847a9902 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } - protected SubmittingPlayer(PlayerConfiguration configuration) + protected SubmittingPlayer(PlayerConfiguration configuration = null) : base(configuration) { } From 571124669daf940ef35bc5a6a81cd8b64fa02bc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 15:45:22 +0900 Subject: [PATCH 095/120] Remove all references to "score submission" from Player --- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 20 +++++++++---------- osu.Game/Screens/Play/SubmittingPlayer.cs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 2ba04b75d6..3797adf360 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -133,9 +133,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private void onResultsReady() => resultsReady.SetResult(true); - protected override async Task SubmitScore(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - await base.SubmitScore(score).ConfigureAwait(false); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); await client.ChangeState(MultiplayerUserState.FinishedPlay).ConfigureAwait(false); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4cf0274614..efe5d26409 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -559,7 +559,7 @@ namespace osu.Game.Screens.Play } private ScheduledDelegate completionProgressDelegate; - private Task scoreSubmissionTask; + private Task prepareScoreForDisplayTask; private void updateCompletionState(ValueChangedEvent completionState) { @@ -586,17 +586,17 @@ namespace osu.Game.Screens.Play if (!Configuration.ShowResults) return; - scoreSubmissionTask ??= Task.Run(async () => + prepareScoreForDisplayTask ??= Task.Run(async () => { var score = CreateScore(); try { - await SubmitScore(score).ConfigureAwait(false); + await PrepareScoreForResultsAsync(score).ConfigureAwait(false); } catch (Exception ex) { - Logger.Error(ex, "Score submission failed!"); + Logger.Error(ex, "Score preparation failed!"); } try @@ -617,7 +617,7 @@ namespace osu.Game.Screens.Play private void scheduleCompletion() => completionProgressDelegate = Schedule(() => { - if (!scoreSubmissionTask.IsCompleted) + if (!prepareScoreForDisplayTask.IsCompleted) { scheduleCompletion(); return; @@ -625,7 +625,7 @@ namespace osu.Game.Screens.Play // screen may be in the exiting transition phase. if (this.IsCurrentScreen()) - this.Push(CreateResults(scoreSubmissionTask.Result)); + this.Push(CreateResults(prepareScoreForDisplayTask.Result)); }); protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; @@ -895,11 +895,11 @@ namespace osu.Game.Screens.Play } /// - /// Submits the player's . + /// Prepare the for display at results. /// - /// The to submit. - /// The submitted score. - protected virtual Task SubmitScore(Score score) => Task.CompletedTask; + /// The to prepare. + /// A task that prepares the provided score. On completion, the score is assumed to be ready for display. + protected virtual Task PrepareScoreForResultsAsync(Score score) => Task.CompletedTask; /// /// Creates the for a . diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index e7847a9902..d876cad941 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -59,9 +59,9 @@ namespace osu.Game.Screens.Play Thread.Sleep(1000); } - protected override async Task SubmitScore(Score score) + protected override async Task PrepareScoreForResultsAsync(Score score) { - await base.SubmitScore(score).ConfigureAwait(false); + await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); Debug.Assert(Token != null); From 3cd8bf2d7f30d2be87d7e67acdcae1a4cfef69f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:05:40 +0900 Subject: [PATCH 096/120] Move token request construction to LoadAsyncComplete to better allow DI usage --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 5 +++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 3797adf360..a5adcdb8ad 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -61,6 +61,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add); HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue }); + } + + protected override void LoadAsyncComplete() + { + base.LoadAsyncComplete(); if (Token == null) return; // Todo: Somehow handle token retrieval failure. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index d876cad941..356f70f4bf 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -28,11 +28,11 @@ namespace osu.Game.Screens.Play { } - [BackgroundDependencyLoader] - private void load() + protected override void LoadAsyncComplete() { - Token = null; + base.LoadAsyncComplete(); + // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. bool failed = false; var req = CreateTokenRequestRequest(); From 242b847516cdf53f4f2c54fdd7097b9ec862f9a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:41:36 +0900 Subject: [PATCH 097/120] Add flow for allowing gameplay to continue even when an error occurs with token retrieval --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 71 +++++++++++++------ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index c695e99874..6ef39e4b75 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -27,6 +27,6 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); - public override APIRequest CreateSubmissionRequest(Score score, int token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); + protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 356f70f4bf..55f4ba4b9b 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -1,8 +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.Threading; +using System; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -30,40 +29,70 @@ namespace osu.Game.Screens.Play protected override void LoadAsyncComplete() { - base.LoadAsyncComplete(); - // Token request construction should happen post-load to allow derived classes to potentially prepare DI backings that are used to create the request. - bool failed = false; + var tcs = new TaskCompletionSource(); + + if (!api.IsLoggedIn) + { + fail(new InvalidOperationException("API is not online.")); + return; + } var req = CreateTokenRequestRequest(); - req.Success += r => Token = r.ID; - req.Failure += e => + + if (req == null) { - failed = true; + fail(new InvalidOperationException("Request could not be constructed.")); + return; + } - if (string.IsNullOrEmpty(e.Message)) - Logger.Error(e, "Failed to retrieve a score submission token."); - else - Logger.Log($"You are not able to submit a score: {e.Message}", level: LogLevel.Important); - - Schedule(() => - { - ValidForResume = false; - this.Exit(); - }); + req.Success += r => + { + Token = r.ID; + tcs.SetResult(true); }; + req.Failure += fail; api.Queue(req); - while (!failed && !Token.HasValue) - Thread.Sleep(1000); + tcs.Task.Wait(); + + void fail(Exception exception) + { + if (HandleTokenRetrievalFailure(exception)) + { + if (string.IsNullOrEmpty(exception.Message)) + Logger.Error(exception, "Failed to retrieve a score submission token."); + else + Logger.Log($"You are not able to submit a score: {exception.Message}", level: LogLevel.Important); + + Schedule(() => + { + ValidForResume = false; + this.Exit(); + }); + } + + tcs.SetResult(false); + } + + base.LoadAsyncComplete(); } + /// + /// Called when a token could not be retrieved for submission. + /// + /// The error causing the failure. + /// Whether gameplay should be immediately exited as a result. Returning false allows the gameplay session to continue. Defaults to true. + protected virtual bool HandleTokenRetrievalFailure(Exception exception) => true; + protected override async Task PrepareScoreForResultsAsync(Score score) { await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); - Debug.Assert(Token != null); + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). + if (Token == null) + return; var tcs = new TaskCompletionSource(); var request = CreateSubmissionRequest(score, Token.Value); From e649a330a405d7d58490e50ea8c7f3b17cb3e27c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 16:41:52 +0900 Subject: [PATCH 098/120] Implement SoloPlayer's request construction --- osu.Game/Screens/Play/SoloPlayer.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index f2f97c5d0d..3dc9df146e 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -1,22 +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 System; +using System.Diagnostics; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Online.Solo; using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { - public override APIRequest CreateSubmissionRequest(Score score, int token) + protected override APIRequest CreateSubmissionRequest(Score score, long token) { - throw new System.NotImplementedException(); + Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + + return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } protected override APIRequest CreateTokenRequestRequest() { - throw new System.NotImplementedException(); + if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) + return null; + + return new CreateSoloScoreRequest(beatmapId, Game.VersionHash); } + + protected override bool HandleTokenRetrievalFailure(Exception exception) => false; } } From 64e85ba995a20dcf1cd3eb6f95fc65ae1a47a0cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 19:19:07 +0900 Subject: [PATCH 099/120] Always fade out approach circles at a HitObject's start time to better match stable --- .../Objects/Drawables/DrawableHitCircle.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 77094f928b..189003875d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -164,28 +164,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApproachCircle.Expire(true); } + protected override void UpdateStartTimeStateTransforms() + { + base.UpdateStartTimeStateTransforms(); + + ApproachCircle.FadeOut(50); + } + protected override void UpdateHitStateTransforms(ArmedState state) { Debug.Assert(HitObject.HitWindows != null); + // todo: temporary / arbitrary, used for lifetime optimisation. + this.Delay(800).FadeOut(); + switch (state) { case ArmedState.Idle: - this.Delay(HitObject.TimePreempt).FadeOut(500); HitArea.HitAction = null; break; case ArmedState.Miss: - ApproachCircle.FadeOut(50); this.FadeOut(100); break; - - case ArmedState.Hit: - ApproachCircle.FadeOut(50); - - // todo: temporary / arbitrary - this.Delay(800).FadeOut(); - break; } Expire(); From d10ff615feeadb9ece5d1f67863ddb9d26152bdc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Mar 2021 19:22:37 +0900 Subject: [PATCH 100/120] Fix default skin's glow resetting fade on miss --- osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs index fcbe4c1b28..46aeadc59b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/MainCirclePiece.cs @@ -74,10 +74,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateState(DrawableHitObject drawableObject, ArmedState state) { - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime, true)) - { + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) glow.FadeOut(400); + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { switch (state) { case ArmedState.Hit: From d17c431faf92affd103a85b46f0a79fdf633040e Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 23 Mar 2021 23:22:17 +0100 Subject: [PATCH 101/120] Disable relative mode for TournamentGame --- osu.Game.Tournament/TournamentGame.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index fadb821bef..bf43b198c4 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -2,18 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System.Drawing; -using osu.Framework.Extensions.Color4Extensions; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics.Cursor; -using osu.Game.Tournament.Models; +using osu.Framework.Input.Handlers.Mouse; +using osu.Framework.Platform; using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -36,7 +39,7 @@ namespace osu.Game.Tournament private LoadingSpinner loadingSpinner; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager frameworkConfig) + private void load(FrameworkConfigManager frameworkConfig, GameHost host) { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); windowMode = frameworkConfig.GetBindable(FrameworkSetting.WindowMode); @@ -48,6 +51,9 @@ namespace osu.Game.Tournament Margin = new MarginPadding(40), }); + var m = (MouseHandler)host.AvailableInputHandlers.Single(t => t is MouseHandler); + m.UseRelativeMode.Value = false; + loadingSpinner.Show(); BracketLoadTask.ContinueWith(_ => LoadComponentsAsync(new[] From fbb992fc7e432900ce9090d98446e6e3e18588ae Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:18:32 -0500 Subject: [PATCH 102/120] Added a comment to new method --- osu.Game/OsuGame.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a52899433a..e8284c0bad 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1008,6 +1008,7 @@ namespace osu.Game Exit(); } protected override IDictionary GetFrameworkConfigDefaults() { + // Overriding settings determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); return defaultOverrides; From 67a03ebc2371c2bb6c14b721fd03f2627cb58b92 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:31:16 -0500 Subject: [PATCH 103/120] Fixed formatting issues to be in line with osu coding standards --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e8284c0bad..ca8fa9f1f6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1007,7 +1007,9 @@ namespace osu.Game if (newScreen == null) Exit(); } - protected override IDictionary GetFrameworkConfigDefaults() { + + protected override IDictionary GetFrameworkConfigDefaults() + { // Overriding settings determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); From 437dadc85f0927936004abc72bc5331289d7e333 Mon Sep 17 00:00:00 2001 From: Owen Young Date: Tue, 23 Mar 2021 19:37:55 -0500 Subject: [PATCH 104/120] Changed comment on GetFrameworkConfigDefaults() to be more accurate --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ca8fa9f1f6..f211723c59 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1010,7 +1010,7 @@ namespace osu.Game protected override IDictionary GetFrameworkConfigDefaults() { - // Overriding settings determined by Framework + // Overriding config defaults determined by Framework IDictionary defaultOverrides = new Dictionary(); defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); return defaultOverrides; From a1c35677efb5e68f5691896b8ca6dfb3763b12e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:02:17 +0900 Subject: [PATCH 105/120] Add more xmldoc --- osu.Game/Screens/Play/SubmittingPlayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 55f4ba4b9b..24d540fbf3 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -17,6 +17,9 @@ namespace osu.Game.Screens.Play /// public abstract class SubmittingPlayer : Player { + /// + /// The token to be used for the current submission. This is fetched via a request created by . + /// protected long? Token { get; private set; } [Resolved] From 8bed7748d696666bfe0023f78710cf84440bc244 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:02:37 +0900 Subject: [PATCH 106/120] Rename token request method to avoid double Request terminology --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 2 +- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- osu.Game/Screens/Play/SubmittingPlayer.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index 6ef39e4b75..d7b49bf2cb 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequestRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + protected override APIRequest CreateTokenRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 3dc9df146e..5c465b6d8f 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } - protected override APIRequest CreateTokenRequestRequest() + protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) return null; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 24d540fbf3..55b10abd5c 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play public abstract class SubmittingPlayer : Player { /// - /// The token to be used for the current submission. This is fetched via a request created by . + /// The token to be used for the current submission. This is fetched via a request created by . /// protected long? Token { get; private set; } @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play return; } - var req = CreateTokenRequestRequest(); + var req = CreateTokenRequest(); if (req == null) { @@ -118,6 +118,6 @@ namespace osu.Game.Screens.Play protected abstract APIRequest CreateSubmissionRequest(Score score, long token); - protected abstract APIRequest CreateTokenRequestRequest(); + protected abstract APIRequest CreateTokenRequest(); } } From e372e355efba030da6866f61fad35abca462cd6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:12:51 +0900 Subject: [PATCH 107/120] Reorder overrides in SoloPlayer to better follow chronological request order --- osu.Game/Screens/Play/SoloPlayer.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 5c465b6d8f..ee1ccdc5b3 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -12,15 +12,6 @@ namespace osu.Game.Screens.Play { public class SoloPlayer : SubmittingPlayer { - protected override APIRequest CreateSubmissionRequest(Score score, long token) - { - Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); - - int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; - - return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); - } - protected override APIRequest CreateTokenRequest() { if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) @@ -30,5 +21,14 @@ namespace osu.Game.Screens.Play } protected override bool HandleTokenRetrievalFailure(Exception exception) => false; + + protected override APIRequest CreateSubmissionRequest(Score score, long token) + { + Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null); + + int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value; + + return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); + } } } From a0c6c4da35dfe5cf2a28efa20da64acab28f25ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:17:13 +0900 Subject: [PATCH 108/120] Rename and refactor token request process to be easier to understand --- osu.Game/Screens/Play/SubmittingPlayer.cs | 24 +++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 55b10abd5c..5d087c212d 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -3,6 +3,7 @@ using System; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; @@ -37,7 +38,7 @@ namespace osu.Game.Screens.Play if (!api.IsLoggedIn) { - fail(new InvalidOperationException("API is not online.")); + handleFailure(new InvalidOperationException("API is not online.")); return; } @@ -45,7 +46,7 @@ namespace osu.Game.Screens.Play if (req == null) { - fail(new InvalidOperationException("Request could not be constructed.")); + handleFailure(new InvalidOperationException("Request could not be constructed.")); return; } @@ -54,13 +55,13 @@ namespace osu.Game.Screens.Play Token = r.ID; tcs.SetResult(true); }; - req.Failure += fail; + req.Failure += handleFailure; api.Queue(req); tcs.Task.Wait(); - void fail(Exception exception) + void handleFailure(Exception exception) { if (HandleTokenRetrievalFailure(exception)) { @@ -116,8 +117,19 @@ namespace osu.Game.Screens.Play await tcs.Task.ConfigureAwait(false); } - protected abstract APIRequest CreateSubmissionRequest(Score score, long token); - + /// + /// Construct a request to be used for retrieval of the score token. + /// Can return null, at which point will be fired. + /// + [CanBeNull] protected abstract APIRequest CreateTokenRequest(); + + /// + /// Construct a request to submit the score. + /// Will only be invoked if the request constructed via was successful. + /// + /// The score to be submitted. + /// The submission token. + protected abstract APIRequest CreateSubmissionRequest(Score score, long token); } } From 84b2f9a848c0c61b047f5eb463e7848cdbd9b03a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:20:44 +0900 Subject: [PATCH 109/120] Make token private --- .../Multiplayer/MultiplayerPlayer.cs | 4 ++-- osu.Game/Screens/Play/SubmittingPlayer.cs | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index a5adcdb8ad..aaacf891bb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -67,8 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadAsyncComplete(); - if (Token == null) - return; // Todo: Somehow handle token retrieval failure. + if (!ValidForResume) + return; // token retrieval may have failed. client.MatchStarted += onMatchStarted; client.ResultsReady += onResultsReady; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 5d087c212d..87a4eb5efe 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Play /// /// The token to be used for the current submission. This is fetched via a request created by . /// - protected long? Token { get; private set; } + private long? token; [Resolved] private IAPIProvider api { get; set; } @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play if (!api.IsLoggedIn) { - handleFailure(new InvalidOperationException("API is not online.")); + handleTokenFailure(new InvalidOperationException("API is not online.")); return; } @@ -46,22 +46,24 @@ namespace osu.Game.Screens.Play if (req == null) { - handleFailure(new InvalidOperationException("Request could not be constructed.")); + handleTokenFailure(new InvalidOperationException("Request could not be constructed.")); return; } req.Success += r => { - Token = r.ID; + token = r.ID; tcs.SetResult(true); }; - req.Failure += handleFailure; + req.Failure += handleTokenFailure; api.Queue(req); tcs.Task.Wait(); - void handleFailure(Exception exception) + base.LoadAsyncComplete(); + + void handleTokenFailure(Exception exception) { if (HandleTokenRetrievalFailure(exception)) { @@ -79,8 +81,6 @@ namespace osu.Game.Screens.Play tcs.SetResult(false); } - - base.LoadAsyncComplete(); } /// @@ -95,11 +95,11 @@ namespace osu.Game.Screens.Play await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). - if (Token == null) + if (token == null) return; var tcs = new TaskCompletionSource(); - var request = CreateSubmissionRequest(score, Token.Value); + var request = CreateSubmissionRequest(score, token.Value); request.Success += s => { From d55324585d678603435adcaed815d3d85186037b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:23:23 +0900 Subject: [PATCH 110/120] Change RoomSubmittingPlayer's request implementation to return null on RoomID missing, rather than silently succeeding --- osu.Game/Screens/Play/RoomSubmittingPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs index d7b49bf2cb..7ba12f5db6 100644 --- a/osu.Game/Screens/Play/RoomSubmittingPlayer.cs +++ b/osu.Game/Screens/Play/RoomSubmittingPlayer.cs @@ -25,7 +25,13 @@ namespace osu.Game.Screens.Play PlaylistItem = playlistItem; } - protected override APIRequest CreateTokenRequest() => new CreateRoomScoreRequest(RoomId.Value ?? 0, PlaylistItem.ID, Game.VersionHash); + protected override APIRequest CreateTokenRequest() + { + if (!(RoomId.Value is long roomId)) + return null; + + return new CreateRoomScoreRequest(roomId, PlaylistItem.ID, Game.VersionHash); + } protected override APIRequest CreateSubmissionRequest(Score score, long token) => new SubmitRoomScoreRequest(token, RoomId.Value ?? 0, PlaylistItem.ID, score.ScoreInfo); } From f95175983ae6f4f79a2a27877467e8ef40f5cb22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 13:37:37 +0900 Subject: [PATCH 111/120] Make code more concise and move method to a more appropriate place --- osu.Game/OsuGame.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 53dc900254..dd1fa32ad9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -531,6 +531,13 @@ namespace osu.Game SentryLogger.Dispose(); } + protected override IDictionary GetFrameworkConfigDefaults() + => new Dictionary + { + // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance) + { FrameworkSetting.WindowMode, WindowMode.Fullscreen } + }; + protected override void LoadComplete() { base.LoadComplete(); @@ -1013,13 +1020,5 @@ namespace osu.Game if (newScreen == null) Exit(); } - - protected override IDictionary GetFrameworkConfigDefaults() - { - // Overriding config defaults determined by Framework - IDictionary defaultOverrides = new Dictionary(); - defaultOverrides.Add(FrameworkSetting.WindowMode, WindowMode.Fullscreen); - return defaultOverrides; - } } } From 5ad8dc316fa1aeed2f2dc194c2f9520b7f93ba09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Mar 2021 14:09:15 +0900 Subject: [PATCH 112/120] Add inline comment and improve linq robustness --- osu.Game.Tournament/TournamentGame.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index bf43b198c4..87e23e3404 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -51,8 +51,12 @@ namespace osu.Game.Tournament Margin = new MarginPadding(40), }); - var m = (MouseHandler)host.AvailableInputHandlers.Single(t => t is MouseHandler); - m.UseRelativeMode.Value = false; + // in order to have the OS mouse cursor visible, relative mode needs to be disabled. + // can potentially be removed when https://github.com/ppy/osu-framework/issues/4309 is resolved. + var mouseHandler = host.AvailableInputHandlers.OfType().FirstOrDefault(); + + if (mouseHandler != null) + mouseHandler.UseRelativeMode.Value = false; loadingSpinner.Show(); From fc5719e445750c10ddad28ead84239a9cb0f519d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Mar 2021 21:31:53 +0300 Subject: [PATCH 113/120] Fix SkinManager not handling extensions casing comparsion properly --- osu.Game/Skinning/SkinManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 9257636301..dfc2981c6f 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -104,7 +104,7 @@ namespace osu.Game.Skinning protected override string ComputeHash(SkinInfo item, ArchiveReader reader = null) { // we need to populate early to create a hash based off skin.ini contents - if (item.Name?.Contains(".osk") == true) + if (item.Name?.EndsWith(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(item); if (item.Creator != null && item.Creator != unknown_creator_string) @@ -122,7 +122,7 @@ namespace osu.Game.Skinning { await base.Populate(model, archive, cancellationToken).ConfigureAwait(false); - if (model.Name?.Contains(".osk") == true) + if (model.Name?.EndsWith(".osk", StringComparison.OrdinalIgnoreCase) == true) populateMetadata(model); } @@ -137,7 +137,7 @@ namespace osu.Game.Skinning } else { - item.Name = item.Name.Replace(".osk", ""); + item.Name = item.Name.Replace(".osk", "", StringComparison.OrdinalIgnoreCase); item.Creator ??= unknown_creator_string; } } From 35810bb2fb98b7f310d2e19b29a8369e1ddb99a8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 24 Mar 2021 22:55:15 +0300 Subject: [PATCH 114/120] Add test coverage --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index a5b4b04ef5..8124bd4199 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -113,6 +113,31 @@ namespace osu.Game.Tests.Skins.IO } } + [Test] + public async Task TestImportUpperCasedOskArchive() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportSkinTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var imported = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.OsK")); + + Assert.That(imported.Name, Is.EqualTo("name 1")); + Assert.That(imported.Creator, Is.EqualTo("author 1")); + + var imported2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOsk("name 1", "author 1"), "skin1.oSK")); + + Assert.That(imported2.Hash, Is.EqualTo(imported.Hash)); + } + finally + { + host.Exit(); + } + } + } + private MemoryStream createOsk(string name, string author) { var zipStream = new MemoryStream(); From 8753d45b71baab76b83f6245e5e8ad98c3ea3e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 12:32:51 +0900 Subject: [PATCH 115/120] Remove duplicate crash report issue template --- .github/ISSUE_TEMPLATE/02-crash-issues.md | 20 ------------------- ...issues.md => 02-feature-request-issues.md} | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/02-crash-issues.md rename .github/ISSUE_TEMPLATE/{03-feature-request-issues.md => 02-feature-request-issues.md} (62%) diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md deleted file mode 100644 index 04170312d1..0000000000 --- a/.github/ISSUE_TEMPLATE/02-crash-issues.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Crash Report -about: Issues regarding crashes or permanent freezes. ---- -**Describe the crash:** - -**Screenshots or videos showing encountered issue:** - -**osu!lazer version:** - -**Logs:** - - -**Computer Specifications:** diff --git a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md similarity index 62% rename from .github/ISSUE_TEMPLATE/03-feature-request-issues.md rename to .github/ISSUE_TEMPLATE/02-feature-request-issues.md index 54c4ff94e5..c3357dd780 100644 --- a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md @@ -1,6 +1,6 @@ --- name: Feature Request -about: Features you would like to see in the game! +about: Propose a feature you would like to see in the game! --- **Describe the new feature:** From f8f461a7a4b2c50edca8d0a2f3c9318d244394a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Mar 2021 12:33:07 +0900 Subject: [PATCH 116/120] Include blurb in issue template to hopefully help people find existing reports --- .github/ISSUE_TEMPLATE/01-bug-issues.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md index 6050036cbf..e45893b97a 100644 --- a/.github/ISSUE_TEMPLATE/01-bug-issues.md +++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md @@ -1,7 +1,18 @@ --- name: Bug Report -about: Issues regarding encountered bugs. +about: Report a bug or crash to desktop --- + + + + **Describe the bug:** **Screenshots or videos showing encountered issue:** @@ -9,6 +20,7 @@ about: Issues regarding encountered bugs. **osu!lazer version:** **Logs:** +