Merge branch 'master' into named-pipe

This commit is contained in:
Dean Herbert 2024-12-07 10:42:55 +09:00
commit e22f3b7d05
No known key found for this signature in database
58 changed files with 464 additions and 378 deletions

View File

@ -1,57 +0,0 @@
# .NET Code Style
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
# IDE0001: Simplify names
dotnet_diagnostic.IDE0001.severity = warning
# IDE0002: Simplify member access
dotnet_diagnostic.IDE0002.severity = warning
# IDE0003: Remove qualification
dotnet_diagnostic.IDE0003.severity = warning
# IDE0004: Remove unnecessary cast
dotnet_diagnostic.IDE0004.severity = warning
# IDE0005: Remove unnecessary imports
dotnet_diagnostic.IDE0005.severity = warning
# IDE0034: Simplify default literal
dotnet_diagnostic.IDE0034.severity = warning
# IDE0036: Sort modifiers
dotnet_diagnostic.IDE0036.severity = warning
# IDE0040: Add accessibility modifier
dotnet_diagnostic.IDE0040.severity = warning
# IDE0049: Use keyword for type name
dotnet_diagnostic.IDE0040.severity = warning
# IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = warning
# IDE0051: Private method is unused
dotnet_diagnostic.IDE0051.severity = silent
# IDE0052: Private member is unused
dotnet_diagnostic.IDE0052.severity = silent
# IDE0073: File header
dotnet_diagnostic.IDE0073.severity = warning
# IDE0130: Namespace mismatch with folder
dotnet_diagnostic.IDE0130.severity = warning
# IDE1006: Naming style
dotnet_diagnostic.IDE1006.severity = warning
#Disable operator overloads requiring alternate named methods
dotnet_diagnostic.CA2225.severity = none
# Banned APIs
dotnet_diagnostic.RS0030.severity = error
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
# See: https://github.com/ppy/osu/pull/19677
dotnet_diagnostic.OSUF001.severity = none

View File

@ -14,10 +14,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.

View File

@ -0,0 +1,109 @@
# .NET Code Style
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
is_global = true
# IDE0001: Simplify names
dotnet_diagnostic.IDE0001.severity = warning
# IDE0002: Simplify member access
dotnet_diagnostic.IDE0002.severity = warning
# IDE0003: Remove qualification
dotnet_diagnostic.IDE0003.severity = warning
# IDE0004: Remove unnecessary cast
dotnet_diagnostic.IDE0004.severity = warning
# IDE0005: Remove unnecessary imports
dotnet_diagnostic.IDE0005.severity = warning
# IDE0034: Simplify default literal
dotnet_diagnostic.IDE0034.severity = warning
# IDE0036: Sort modifiers
dotnet_diagnostic.IDE0036.severity = warning
# IDE0040: Add accessibility modifier
dotnet_diagnostic.IDE0040.severity = warning
# IDE0049: Use keyword for type name
dotnet_diagnostic.IDE0040.severity = warning
# IDE0055: Fix formatting
dotnet_diagnostic.IDE0055.severity = warning
# IDE0051: Private method is unused
dotnet_diagnostic.IDE0051.severity = silent
# IDE0052: Private member is unused
dotnet_diagnostic.IDE0052.severity = silent
# IDE0073: File header
dotnet_diagnostic.IDE0073.severity = warning
# IDE0130: Namespace mismatch with folder
dotnet_diagnostic.IDE0130.severity = warning
# IDE1006: Naming style
dotnet_diagnostic.IDE1006.severity = warning
# CA1305: Specify IFormatProvider
# Too many noisy warnings for parsing/formatting numbers
dotnet_diagnostic.CA1305.severity = none
# CA1507: Use nameof to express symbol names
# Flaggs serialization name attributes
dotnet_diagnostic.CA1507.severity = suggestion
# CA1806: Do not ignore method results
# The usages for numeric parsing are explicitly optional
dotnet_diagnostic.CA1806.severity = suggestion
# CA1822: Mark members as static
# Potential false positive around reflection/too much noise
dotnet_diagnostic.CA1822.severity = none
# CA1826: Do not use Enumerable method on indexable collections
dotnet_diagnostic.CA1826.severity = suggestion
# CA1859: Use concrete types when possible for improved performance
# Involves design considerations
dotnet_diagnostic.CA1859.severity = suggestion
# CA1860: Avoid using 'Enumerable.Any()' extension method
dotnet_diagnostic.CA1860.severity = suggestion
# CA1861: Avoid constant arrays as arguments
# Outdated with collection expressions
dotnet_diagnostic.CA1861.severity = suggestion
# CA2007: Consider calling ConfigureAwait on the awaited task
dotnet_diagnostic.CA2007.severity = warning
# CA2016: Forward the 'CancellationToken' parameter to methods
# Some overloads are having special handling for debugger
dotnet_diagnostic.CA2016.severity = suggestion
# CA2021: Do not call Enumerable.Cast<T> or Enumerable.OfType<T> with incompatible types
# Causing a lot of false positives with generics
dotnet_diagnostic.CA2021.severity = none
# CA2101: Specify marshaling for P/Invoke string arguments
# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport
dotnet_diagnostic.CA2101.severity = none
# CA2201: Do not raise reserved exception types
dotnet_diagnostic.CA2201.severity = warning
# CA2208: Instantiate argument exceptions correctly
dotnet_diagnostic.CA2208.severity = suggestion
# CA2242: Test for NaN correctly
dotnet_diagnostic.CA2242.severity = warning
# Banned APIs
dotnet_diagnostic.RS0030.severity = error
# Temporarily disable analysing CanBeNull = true in NRT contexts due to mobile issues.
# See: https://github.com/ppy/osu/pull/19677
dotnet_diagnostic.OSUF001.severity = none

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
<Rule Id="CA1016" Action="None" />
<Rule Id="CA1028" Action="None" />
<Rule Id="CA1031" Action="None" />
<Rule Id="CA1034" Action="None" />
<Rule Id="CA1036" Action="None" />
<Rule Id="CA1040" Action="None" />
<Rule Id="CA1044" Action="None" />
<Rule Id="CA1051" Action="None" />
<Rule Id="CA1054" Action="None" />
<Rule Id="CA1056" Action="None" />
<Rule Id="CA1062" Action="None" />
<Rule Id="CA1063" Action="None" />
<Rule Id="CA1067" Action="None" />
<Rule Id="CA1707" Action="None" />
<Rule Id="CA1710" Action="None" />
<Rule Id="CA1714" Action="None" />
<Rule Id="CA1716" Action="None" />
<Rule Id="CA1717" Action="None" />
<Rule Id="CA1720" Action="None" />
<Rule Id="CA1721" Action="None" />
<Rule Id="CA1724" Action="None" />
<Rule Id="CA1801" Action="None" />
<Rule Id="CA1806" Action="None" />
<Rule Id="CA1812" Action="None" />
<Rule Id="CA1814" Action="None" />
<Rule Id="CA1815" Action="None" />
<Rule Id="CA1819" Action="None" />
<Rule Id="CA1822" Action="None" />
<Rule Id="CA1823" Action="None" />
<Rule Id="CA2007" Action="Warning" />
<Rule Id="CA2214" Action="None" />
<Rule Id="CA2227" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.CodeQuality.CSharp.Analyzers" RuleNamespace="Microsoft.CodeQuality.CSharp.Analyzers">
<Rule Id="CA1001" Action="None" />
<Rule Id="CA1032" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.NetCore.Analyzers" RuleNamespace="Microsoft.NetCore.Analyzers">
<Rule Id="CA1303" Action="None" />
<Rule Id="CA1304" Action="None" />
<Rule Id="CA1305" Action="None" />
<Rule Id="CA1307" Action="None" />
<Rule Id="CA1308" Action="None" />
<Rule Id="CA1816" Action="None" />
<Rule Id="CA1826" Action="None" />
<Rule Id="CA2000" Action="None" />
<Rule Id="CA2008" Action="None" />
<Rule Id="CA2213" Action="None" />
<Rule Id="CA2235" Action="None" />
</Rules>
<Rules AnalyzerId="Microsoft.NetCore.CSharp.Analyzers" RuleNamespace="Microsoft.NetCore.CSharp.Analyzers">
<Rule Id="CA1309" Action="Warning" />
<Rule Id="CA2201" Action="Warning" />
</Rules>
</RuleSet>

View File

@ -18,9 +18,21 @@
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<!-- Rider compatibility: .globalconfig needs to be explicitly referenced instead of using the global file name. -->
<GlobalAnalyzerConfigFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\osu.globalconfig" />
</ItemGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
<AnalysisMode>Default</AnalysisMode>
<AnalysisModeDesign>Default</AnalysisModeDesign>
<AnalysisModeDocumentation>Recommended</AnalysisModeDocumentation>
<AnalysisModeGlobalization>Recommended</AnalysisModeGlobalization>
<AnalysisModeInteroperability>Recommended</AnalysisModeInteroperability>
<AnalysisModeMaintainability>Recommended</AnalysisModeMaintainability>
<AnalysisModeNaming>Default</AnalysisModeNaming>
<AnalysisModePerformance>Minimum</AnalysisModePerformance>
<AnalysisModeReliability>Recommended</AnalysisModeReliability>
<AnalysisModeSecurity>Default</AnalysisModeSecurity>
<AnalysisModeUsage>Default</AnalysisModeUsage>
</PropertyGroup>
<PropertyGroup Label="Documentation">
<GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1128.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1205.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.

View File

@ -148,15 +148,7 @@ namespace osu.Desktop.Windows
foreach (var association in uri_associations)
association.UpdateDescription(getLocalisedString(association.Description));
string getLocalisedString(LocalisableString s)
{
if (localisation == null)
return s.ToString();
var b = localisation.GetLocalisedBindableString(s);
b.UnbindAll();
return b.Value;
}
string getLocalisedString(LocalisableString s) => localisation?.GetLocalisedString(s) ?? s.ToString();
}
#region Native interop

View File

@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 1: return colour_cyan;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 3:
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 2: return colour_cyan;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 4:
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 3: return colour_purple;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 5:
@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 4: return colour_cyan;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 6:
@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 5: return colour_pink;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 7:
@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 6: return colour_pink;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 8:
@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 7: return colour_purple;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 9:
@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 8: return colour_purple;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
case 10:
@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 9: return colour_purple;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
}
@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case 5: return colour_green;
default: throw new ArgumentOutOfRangeException();
default: throw new ArgumentOutOfRangeException(nameof(columnIndex));
}
}
}

View File

@ -0,0 +1,7 @@
# Higher global_level has higher priority, the default global_level
# is 100 for root .globalconfig and 0 for others
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files#precedence
is_global = true
global_level = 101
dotnet_diagnostic.CA2007.severity = none

View File

@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay
string? filePath = null;
// Files starting with _ are temporary, created by CreateFileSafely call.
AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith("_", StringComparison.Ordinal)), () => Is.Not.Null);
AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith('_')), () => Is.Not.Null);
AddUntilStep("filesize is non-zero", () =>
{
try

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Mods
protected override TestPlayer CreateModPlayer(Ruleset ruleset)
{
var player = base.CreateModPlayer(ruleset);
player.RestartRequested = _ => restartRequested = true;
player.PrepareLoaderForRestart = _ => restartRequested = true;
return player;
}

View File

@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("make second user host", () => MultiplayerClient.TransferHost(3));
AddUntilStep("kick buttons not visible", () => this.ChildrenOfType<ParticipantPanel.KickButton>().Count(d => d.IsPresent) == 0);
AddUntilStep("kick buttons not visible", () => !this.ChildrenOfType<ParticipantPanel.KickButton>().Any(d => d.IsPresent));
AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id));

View File

@ -101,7 +101,16 @@ namespace osu.Game.Tests.Visual.UserInterface
}
}
}
}
},
}
},
new OsuMenuItem(@"Another nested option")
{
Items = new MenuItem[]
{
new OsuMenuItem(@"Sub-One"),
new OsuMenuItem(@"Sub-Two"),
new OsuMenuItem(@"Sub-Three"),
}
},
new OsuMenuItem(@"Choose me please"),

View File

@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("click delete option", () =>
{
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete"));
InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType<DrawableOsuMenuItem>().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase)));
InputManager.Click(MouseButton.Left);
});

View File

@ -12,9 +12,9 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup Label="Code Analysis">
<GlobalAnalyzerConfigFiles Include="CodeAnalysis.tests.globalconfig" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="osu! Rule Set" Description=" " ToolsVersion="16.0">
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
<Rule Id="CA2007" Action="None" />
</Rules>
</RuleSet>

View File

@ -248,29 +248,30 @@ namespace osu.Game.Database
return new RealmLive<T>(realmObject, realm);
}
#pragma warning disable RS0030 // mentioning banned symbols in documentation
/// <summary>
/// Register a callback to be invoked each time this <see cref="T:Realms.IRealmCollection`1" /> changes.
/// Register a callback to be invoked each time this <see cref="IRealmCollection{T}" /> changes.
/// </summary>
/// <remarks>
/// <para>
/// This adds osu! specific thread and managed state safety checks on top of <see cref="IRealmCollection{T}.SubscribeForNotifications"/>.
/// </para>
/// <para>
/// The first callback will be invoked with the initial <see cref="T:Realms.IRealmCollection`1" /> after the asynchronous query completes,
/// The first callback will be invoked with the initial <see cref="IRealmCollection{T}" /> after the asynchronous query completes,
/// and then called again after each write transaction which changes either any of the objects in the collection, or
/// which objects are in the collection. The <c>changes</c> parameter will
/// be <c>null</c> the first time the callback is invoked with the initial results. For each call after that,
/// it will contain information about which rows in the results were added, removed or modified.
/// </para>
/// <para>
/// If a write transaction did not modify any objects in this <see cref="T:Realms.IRealmCollection`1" />, the callback is not invoked at all.
/// If a write transaction did not modify any objects in this <see cref="IRealmCollection{T}" />, the callback is not invoked at all.
/// If an error occurs the callback will be invoked with <c>null</c> for the <c>sender</c> parameter and a non-<c>null</c> <c>error</c>.
/// Currently the only errors that can occur are when opening the <see cref="T:Realms.Realm" /> on the background worker thread.
/// Currently the only errors that can occur are when opening the <see cref="Realm" /> on the background worker thread.
/// </para>
/// <para>
/// At the time when the block is called, the <see cref="T:Realms.IRealmCollection`1" /> object will be fully evaluated
/// At the time when the block is called, the <see cref="IRealmCollection{T}" /> object will be fully evaluated
/// and up-to-date, and as long as you do not perform a write transaction on the same thread
/// or explicitly call <see cref="M:Realms.Realm.Refresh" />, accessing it will never perform blocking work.
/// or explicitly call <see cref="Realm.Refresh" />, accessing it will never perform blocking work.
/// </para>
/// <para>
/// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity.
@ -279,13 +280,14 @@ namespace osu.Game.Database
/// </para>
/// </remarks>
/// <param name="collection">The <see cref="IRealmCollection{T}"/> to observe for changes.</param>
/// <param name="callback">The callback to be invoked with the updated <see cref="T:Realms.IRealmCollection`1" />.</param>
/// <param name="callback">The callback to be invoked with the updated <see cref="IRealmCollection{T}" />.</param>
/// <returns>
/// A subscription token. It must be kept alive for as long as you want to receive change notifications.
/// To stop receiving notifications, call <see cref="M:System.IDisposable.Dispose" />.
/// To stop receiving notifications, call <see cref="IDisposable.Dispose" />.
/// </returns>
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0})" />
/// <seealso cref="M:Realms.CollectionExtensions.SubscribeForNotifications``1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0})" />
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IList{T}, NotificationCallbackDelegate{T})" />
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IQueryable{T}, NotificationCallbackDelegate{T})" />
#pragma warning restore RS0030
public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback)
where T : RealmObjectBase
{

View File

@ -60,7 +60,7 @@ namespace osu.Game.Extensions
public static string ToCamelCase(this string input)
{
string word = input.ToPascalCase();
return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word;
return word.Length > 0 ? char.ToLowerInvariant(word[0]) + word.Substring(1) : word;
}
/// <summary>

View File

@ -11,16 +11,8 @@ namespace osu.Game.Graphics.Cursor
[Cached(typeof(OsuContextMenuContainer))]
public partial class OsuContextMenuContainer : ContextMenuContainer
{
[Cached]
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
private OsuContextMenu menu = null!;
public OsuContextMenuContainer()
{
AddInternal(samples);
}
protected override Menu CreateMenu() => menu = new OsuContextMenu(true);
public void CloseMenu()

View File

@ -23,6 +23,9 @@ namespace osu.Game.Graphics.UserInterface
DialogCancel,
[Description("dialog-ok")]
DialogOk
DialogOk,
[Description("menu-open")]
MenuOpen,
}
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface
private const int fade_duration = 250;
[Resolved]
private OsuContextMenuSamples samples { get; set; } = null!;
private OsuMenuSamples menuSamples { get; set; } = null!;
// todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed.
private bool wasOpened;
@ -47,15 +47,14 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateOpen()
{
wasOpened = true;
this.FadeIn(fade_duration, Easing.OutQuint);
if (playClickSample)
samples.PlayClickSample();
if (!playClickSample)
return;
if (!wasOpened)
samples.PlayOpenSample();
wasOpened = true;
menuSamples.PlayClickSample();
menuSamples.PlayOpenSample();
}
protected override void AnimateClose()
@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface
this.FadeOut(fade_duration, Easing.OutQuint);
if (wasOpened)
samples.PlayCloseSample();
menuSamples.PlayCloseSample();
wasOpened = false;
}

View File

@ -1,37 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public partial class OsuContextMenuSamples : Component
{
private Sample sampleClick;
private Sample sampleOpen;
private Sample sampleClose;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
}
public void PlayClickSample() => Scheduler.AddOnce(playClickSample);
private void playClickSample() => sampleClick.Play();
public void PlayOpenSample() => Scheduler.AddOnce(playOpenSample);
private void playOpenSample() => sampleOpen.Play();
public void PlayCloseSample() => Scheduler.AddOnce(playCloseSample);
private void playCloseSample() => sampleClose.Play();
}
}

View File

@ -4,8 +4,6 @@
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -20,12 +18,12 @@ namespace osu.Game.Graphics.UserInterface
{
public partial class OsuMenu : Menu
{
private Sample sampleOpen;
private Sample sampleClose;
// todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed.
private bool wasOpened;
[Resolved]
private OsuMenuSamples menuSamples { get; set; } = null!;
public OsuMenu(Direction direction, bool topLevelMenu = false)
: base(direction, topLevelMenu)
{
@ -33,13 +31,8 @@ namespace osu.Game.Graphics.UserInterface
MaskingContainer.CornerRadius = 4;
ItemsContainer.Padding = new MarginPadding(5);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close");
OnSubmenuOpen += _ => { menuSamples?.PlaySubOpenSample(); };
}
protected override void Update()
@ -64,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateOpen()
{
if (!TopLevelMenu && !wasOpened)
sampleOpen?.Play();
menuSamples?.PlayOpenSample();
this.FadeIn(300, Easing.OutQuint);
wasOpened = true;
@ -73,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void AnimateClose()
{
if (!TopLevelMenu && wasOpened)
sampleClose?.Play();
menuSamples?.PlayCloseSample();
this.FadeOut(300, Easing.OutQuint);
wasOpened = false;

View File

@ -0,0 +1,70 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public partial class OsuMenuSamples : Component
{
private Sample sampleClick;
private Sample sampleOpen;
private Sample sampleSubOpen;
private Sample sampleClose;
private bool triggerOpen;
private bool triggerSubOpen;
private bool triggerClose;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleClick = audio.Samples.Get(@"UI/menu-open-select");
sampleOpen = audio.Samples.Get(@"UI/menu-open");
sampleSubOpen = audio.Samples.Get(@"UI/menu-sub-open");
sampleClose = audio.Samples.Get(@"UI/menu-close");
}
public void PlayClickSample()
{
Scheduler.AddOnce(playClickSample);
}
public void PlayOpenSample()
{
triggerOpen = true;
Scheduler.AddOnce(resolvePlayback);
}
public void PlaySubOpenSample()
{
triggerSubOpen = true;
Scheduler.AddOnce(resolvePlayback);
}
public void PlayCloseSample()
{
triggerClose = true;
Scheduler.AddOnce(resolvePlayback);
}
private void playClickSample() => sampleClick.Play();
private void resolvePlayback()
{
if (triggerSubOpen)
sampleSubOpen?.Play();
else if (triggerOpen)
sampleOpen?.Play();
else if (triggerClose)
sampleClose?.Play();
triggerOpen = triggerSubOpen = triggerClose = false;
}
}
}

View File

@ -9,21 +9,6 @@ namespace osu.Game.Localisation
{
private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
/// <summary>
/// "Debug"
/// </summary>
public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
/// <summary>
/// "Show log overlay"
/// </summary>
public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
/// <summary>
/// "Bypass front-to-back render pass"
/// </summary>
public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
/// <summary>
/// "Import files"
/// </summary>
@ -34,16 +19,6 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier");
/// <summary>
/// "Memory"
/// </summary>
public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
/// <summary>
/// "Clear all caches"
/// </summary>
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
private static string getKey(string key) => $"{prefix}:{key}";
}
}

View File

@ -44,6 +44,11 @@ namespace osu.Game.Localisation
/// </summary>
public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
/// <summary>
/// "Checking for updates"
/// </summary>
public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates");
/// <summary>
/// "Open osu! folder"
/// </summary>

View File

@ -363,7 +363,7 @@ namespace osu.Game.Online.Leaderboards
return null;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(state));
}
}

View File

@ -87,7 +87,7 @@ namespace osu.Game.Online
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(state.NewValue));
}
});

View File

@ -41,6 +41,7 @@ using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.IO;
@ -383,6 +384,10 @@ namespace osu.Game
GlobalActionContainer globalBindings;
OsuMenuSamples menuSamples;
dependencies.Cache(menuSamples = new OsuMenuSamples());
base.Content.Add(menuSamples);
base.Content.Add(SafeAreaContainer = new SafeAreaContainer
{
SafeAreaOverrideEdges = SafeAreaOverrideEdges,

View File

@ -126,7 +126,7 @@ namespace osu.Game.Overlays
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(state.NewValue));
}
}
}

View File

@ -386,10 +386,8 @@ namespace osu.Game.Overlays
{
channelList.RemoveChannel(channel);
if (loadedChannels.ContainsKey(channel))
if (loadedChannels.Remove(channel, out var loaded))
{
DrawableChannel loaded = loadedChannels[channel];
loadedChannels.Remove(channel);
// DrawableChannel removed from cache must be manually disposed
loaded.Dispose();
}

View File

@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Comments
protected void OnSuccess(CommentBundle response)
{
commentCounter.Current.Value = response.Total;
newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && m.Type == type.Value.ToString().ToSnakeCase().ToLowerInvariant());
newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && string.Equals(m.Type, type.Value.ToString().ToSnakeCase(), StringComparison.OrdinalIgnoreCase));
if (!response.Comments.Any())
{

View File

@ -5,6 +5,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
@ -90,11 +91,13 @@ namespace osu.Game.Overlays.FirstRunSetup
new GraphicsSection(),
new OnlineSection(),
new MaintenanceSection(),
new DebugSection(),
},
SearchTerm = SettingsItem<bool>.CLASSIC_DEFAULT_SEARCH_TERM,
}
};
if (DebugUtils.IsDebugBuild)
searchContainer.Add(new DebugSection());
}
private void applyClassic()

View File

@ -1,19 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.DebugSettings;
namespace osu.Game.Overlays.Settings.Sections
{
public partial class DebugSection : SettingsSection
{
public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader;
public override LocalisableString Header => @"Debug";
public override Drawable CreateIcon() => new SpriteIcon
{
@ -22,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections
public DebugSection()
{
Add(new GeneralSettings());
if (DebugUtils.IsDebugBuild)
Add(new BatchImportSettings());
Add(new MemorySettings());
Children = new Drawable[]
{
new GeneralSettings(),
new BatchImportSettings(),
new MemorySettings(),
};
}
}
}

View File

@ -5,43 +5,28 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Localisation;
using osu.Game.Screens;
using osu.Game.Screens.Import;
using osu.Game.Screens.Utility;
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
{
public partial class GeneralSettings : SettingsSubsection
{
protected override LocalisableString Header => CommonStrings.General;
protected override LocalisableString Header => @"General";
[BackgroundDependencyLoader]
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, IPerformFromScreenRunner? performer)
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig)
{
Children = new Drawable[]
{
new SettingsCheckbox
{
LabelText = DebugSettingsStrings.ShowLogOverlay,
LabelText = @"Show log overlay",
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
},
new SettingsCheckbox
{
LabelText = DebugSettingsStrings.BypassFrontToBackPass,
LabelText = @"Bypass front-to-back render pass",
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
},
new SettingsButton
{
Text = DebugSettingsStrings.ImportFiles,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
},
new SettingsButton
{
Text = DebugSettingsStrings.RunLatencyCertifier,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen()))
}
};
}
}

View File

@ -11,13 +11,12 @@ using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
{
public partial class MemorySettings : SettingsSubsection
{
protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
protected override LocalisableString Header => @"Memory";
[BackgroundDependencyLoader]
private void load(GameHost host, RealmAccess realm)
@ -29,27 +28,27 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
{
new SettingsButton
{
Text = DebugSettingsStrings.ClearAllCaches,
Text = @"Clear all caches",
Action = host.Collect
},
new SettingsButton
{
Text = "Compact realm",
Text = @"Compact realm",
Action = () =>
{
// Blocking operations implicitly causes a Compact().
using (realm.BlockAllOperations("compact"))
using (realm.BlockAllOperations(@"compact"))
{
}
}
},
blockAction = new SettingsButton
{
Text = "Block realm",
Text = @"Block realm",
},
unblockAction = new SettingsButton
{
Text = "Unblock realm",
Text = @"Unblock realm",
},
};
@ -57,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
{
try
{
IDisposable? token = realm.BlockAllOperations("maintenance");
IDisposable? token = realm.BlockAllOperations(@"maintenance");
blockAction.Enabled.Value = false;
@ -89,7 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
}
catch (Exception e)
{
Logger.Error(e, "Blocking realm failed");
Logger.Error(e, @"Blocking realm failed");
}
};
}

View File

@ -4,7 +4,6 @@
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Logging;
@ -13,6 +12,7 @@ using osu.Framework.Screens;
using osu.Framework.Statistics;
using osu.Game.Configuration;
using osu.Game.Localisation;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved]
private Storage storage { get; set; } = null!;
[Resolved]
private OsuGame? game { get; set; }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuGame? game)
private void load(OsuConfigManager config)
{
Add(new SettingsEnumDropdown<ReleaseStream>
{
@ -50,23 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
Add(checkForUpdatesButton = new SettingsButton
{
Text = GeneralSettingsStrings.CheckUpdate,
Action = () =>
{
checkForUpdatesButton.Enabled.Value = false;
Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() =>
{
if (!task.GetResultSafely())
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
checkForUpdatesButton.Enabled.Value = true;
}));
}
Action = () => checkForUpdates().FireAndForget()
});
}
@ -94,6 +81,44 @@ namespace osu.Game.Overlays.Settings.Sections.General
}
}
private async Task checkForUpdates()
{
if (updateManager == null || game == null)
return;
checkForUpdatesButton.Enabled.Value = false;
var checkingNotification = new ProgressNotification
{
Text = GeneralSettingsStrings.CheckingForUpdates,
};
notifications?.Post(checkingNotification);
try
{
bool foundUpdate = await updateManager.CheckForUpdateAsync().ConfigureAwait(true);
if (!foundUpdate)
{
notifications?.Post(new SimpleNotification
{
Text = GeneralSettingsStrings.RunningLatestRelease(game.Version),
Icon = FontAwesome.Solid.CheckCircle,
});
}
}
catch
{
}
finally
{
// This sequence allows the notification to be immediately dismissed.
checkingNotification.State = ProgressNotificationState.Cancelled;
checkingNotification.Close(false);
checkForUpdatesButton.Enabled.Value = true;
}
}
private void exportLogs()
{
ProgressNotification notification = new ProgressNotification

View File

@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys" });
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys", @"buttons" });
public BindingSettings(KeyBindingPanel keyConfig)
{

View File

@ -114,10 +114,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
{
t.NewLine();
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription(
RuntimeInfo.OS == RuntimeInfo.Platform.Windows
? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")));
t.AddLinks(formattedSource.Text, formattedSource.Links);
}
}),

View File

@ -0,0 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Game.Localisation;
using osu.Game.Screens;
using osu.Game.Screens.Import;
using osu.Game.Screens.Utility;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
public partial class GeneralSettings : SettingsSubsection
{
protected override LocalisableString Header => CommonStrings.General;
[BackgroundDependencyLoader]
private void load(IPerformFromScreenRunner? performer)
{
Children = new[]
{
new SettingsButton
{
Text = DebugSettingsStrings.ImportFiles,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
},
new SettingsButton
{
Text = DebugSettingsStrings.RunLatencyCertifier,
Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen()))
}
};
}
}
}

View File

@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
Children = new Drawable[]
{
new GeneralSettings(),
new BeatmapSettings(),
new SkinSettings(),
new CollectionsSettings(),

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -27,21 +28,28 @@ namespace osu.Game.Overlays
public LocalisableString Title => SettingsStrings.HeaderTitle;
public LocalisableString Description => SettingsStrings.HeaderDescription;
protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[]
protected override IEnumerable<SettingsSection> CreateSections()
{
// This list should be kept in sync with ScreenBehaviour.
new GeneralSection(),
new SkinSection(),
new InputSection(createSubPanel(new KeyBindingPanel())),
new UserInterfaceSection(),
new GameplaySection(),
new RulesetSection(),
new AudioSection(),
new GraphicsSection(),
new OnlineSection(),
new MaintenanceSection(),
new DebugSection(),
};
var sections = new List<SettingsSection>
{
// This list should be kept in sync with ScreenBehaviour.
new GeneralSection(),
new SkinSection(),
new InputSection(createSubPanel(new KeyBindingPanel())),
new UserInterfaceSection(),
new GameplaySection(),
new RulesetSection(),
new AudioSection(),
new GraphicsSection(),
new OnlineSection(),
new MaintenanceSection(),
};
if (DebugUtils.IsDebugBuild)
sections.Add(new DebugSection());
return sections;
}
private readonly List<SettingsSubPanel> subPanels = new List<SettingsSubPanel>();

View File

@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Toolbar
break;
default:
throw new ArgumentOutOfRangeException();
throw new ArgumentOutOfRangeException(nameof(state.NewValue));
}
});
}

View File

@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods
};
drawable.OnRevertResult += (_, result) =>
{
if (!ratesForRewinding.ContainsKey(result.HitObject)) return;
if (!ratesForRewinding.TryGetValue(result.HitObject, out double rate)) return;
if (!shouldProcessResult(result)) return;
recentRates.Insert(0, ratesForRewinding[result.HitObject]);
recentRates.Insert(0, rate);
ratesForRewinding.Remove(result.HitObject);
recentRates.RemoveAt(recentRates.Count - 1);

View File

@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
return PathType.CATMULL;
case 'B':
if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0)
if (input.Length > 1 && int.TryParse(input.AsSpan(1), out int degree) && degree > 0)
return PathType.BSpline(degree);
return PathType.BEZIER;

View File

@ -99,7 +99,7 @@ namespace osu.Game.Screens.Edit.Components.Menus
ForegroundColourHover = colourProvider.Content1;
BackgroundColourHover = colourProvider.Background1;
AddInternal(hoverClickSounds = new HoverClickSounds());
AddInternal(hoverClickSounds = new HoverClickSounds(HoverSampleSet.MenuOpen));
}
protected override void LoadComplete()

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -80,19 +81,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
bool matchingFilter = true;
matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false;
if (!string.IsNullOrEmpty(criteria.SearchString))
{
// Room name isn't translatable, so ToString() is used here for simplicity.
matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
}
matchingFilter &= matchPermissions(r, criteria.Permissions);
// Room name isn't translatable, so ToString() is used here for simplicity.
string[] filterTerms = r.FilterTerms.Select(t => t.ToString()).ToArray();
string[] searchTerms = criteria.SearchString.Split(' ', StringSplitOptions.RemoveEmptyEntries);
matchingFilter &= searchTerms.All(searchTerm => filterTerms.Any(filterTerm => checkTerm(filterTerm, searchTerm)));
r.MatchingFilter = matchingFilter;
}
});
// Lifted from SearchContainer.
static bool checkTerm(string haystack, string needle)
{
int index = 0;
for (int i = 0; i < needle.Length; i++)
{
int found = CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle[i], index, CompareOptions.OrdinalIgnoreCase);
if (found < 0)
return false;
index = found + 1;
}
return true;
}
static bool matchPermissions(DrawableLoungeRoom room, RoomPermissionsFilter accessType)
{
switch (accessType)

View File

@ -83,7 +83,7 @@ namespace osu.Game.Screens.Play
/// </summary>
protected virtual bool PauseOnFocusLost => true;
public Action<bool> RestartRequested;
public Action<bool> PrepareLoaderForRestart;
private bool isRestarting;
private bool skipExitTransition;
@ -646,7 +646,6 @@ namespace osu.Game.Screens.Play
// import current score if possible.
prepareAndImportScoreAsync();
// Screen may not be current if a restart has been performed.
if (this.IsCurrentScreen())
{
skipExitTransition = skipTransition;
@ -657,6 +656,12 @@ namespace osu.Game.Screens.Play
// - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance.
this.Exit();
}
else
{
// May be restarting from results screen.
if (this.GetChildScreen() != null)
this.MakeCurrent();
}
return true;
}
@ -719,12 +724,8 @@ namespace osu.Game.Screens.Play
// stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader.
musicController.Stop();
if (RestartRequested != null)
{
skipExitTransition = quickRestart;
RestartRequested?.Invoke(quickRestart);
return true;
}
skipExitTransition = quickRestart;
PrepareLoaderForRestart?.Invoke(quickRestart);
return PerformExit(quickRestart);
}

View File

@ -457,7 +457,7 @@ namespace osu.Game.Screens.Play
CurrentPlayer = createPlayer();
CurrentPlayer.Configuration.AutomaticallySkipIntro |= quickRestart;
CurrentPlayer.RestartCount = restartCount++;
CurrentPlayer.RestartRequested = restartRequested;
CurrentPlayer.PrepareLoaderForRestart = prepareForRestart;
LoadTask = LoadComponentAsync(CurrentPlayer, _ =>
{
@ -470,13 +470,11 @@ namespace osu.Game.Screens.Play
{
}
private void restartRequested(bool quickRestartRequested)
private void prepareForRestart(bool quickRestartRequested)
{
quickRestart = quickRestartRequested;
hideOverlays = true;
ValidForResume = true;
this.MakeCurrent();
}
private void contentIn(double delayBeforeSideDisplays = 0)

View File

@ -55,6 +55,8 @@ namespace osu.Game.Screens.Ranking
[Resolved]
private Player? player { get; set; }
private bool skipExitTransition;
[Resolved]
private IAPIProvider api { get; set; } = null!;
@ -203,6 +205,7 @@ namespace osu.Game.Screens.Ranking
{
if (!this.IsCurrentScreen()) return;
skipExitTransition = true;
player?.Restart(true);
},
});
@ -313,7 +316,8 @@ namespace osu.Game.Screens.Ranking
// HitObject references from HitEvent.
Score?.HitEvents.Clear();
this.FadeOut(100);
if (!skipExitTransition)
this.FadeOut(100);
return false;
}

View File

@ -59,14 +59,14 @@ namespace osu.Game.Screens.Ranking.Statistics.User
new SimpleStatisticTable.Spacer(),
new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
},
new Drawable[] { },
[],
new Drawable[]
{
new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
new SimpleStatisticTable.Spacer(),
new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },
},
new Drawable[] { },
[],
new Drawable[]
{
new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } },

View File

@ -37,7 +37,7 @@ namespace osu.Game.Skinning
protected override string[] HashableFileTypes => new[] { ".ini", ".json" };
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == @".osk";
protected override bool ShouldDeleteArchive(string path) => string.Equals(Path.GetExtension(path), @".osk", StringComparison.OrdinalIgnoreCase);
protected override SkinInfo CreateModel(ArchiveReader archive, ImportParameters parameters) => new SkinInfo { Name = archive.Name ?? @"No name" };

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -91,7 +92,7 @@ namespace osu.Game.Storyboards
// Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints.
backgroundPath = backgroundPath.ToLowerInvariant();
return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath);
return GetLayer("Background").Elements.Any(e => string.Equals(e.Path, backgroundPath, StringComparison.OrdinalIgnoreCase));
}
}

View File

@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator
/// <param name="state">The spectator state to end play with.</param>
public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit)
{
if (!userBeatmapDictionary.TryGetValue(userId, out int value))
if (!userBeatmapDictionary.TryGetValue(userId, out int beatmapId))
return;
((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState
{
BeatmapID = value,
BeatmapID = beatmapId,
RulesetID = 0,
Mods = userModsDictionary[userId],
State = state

View File

@ -666,10 +666,7 @@ namespace osu.Game.Utils
{
// 2020-10-07 jbialogrodzki #730 Since this is public API we should probably
// handle null arguments? It doesn't seem to have been done consistently in this class though.
if (coefficients == null)
{
throw new ArgumentNullException(nameof(coefficients));
}
ArgumentNullException.ThrowIfNull(coefficients);
// 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling.
// Without this check, we attempted to peek coefficients at negative indices!

View File

@ -35,8 +35,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.1128.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1106.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.1205.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1202.0" />
<PackageReference Include="Sentry" Version="4.13.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.38.0" />

View File

@ -17,6 +17,6 @@
<MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1128.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1205.0" />
</ItemGroup>
</Project>

View File

@ -56,11 +56,10 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.globalconfig = .globalconfig
Directory.Build.props = Directory.Build.props
osu.Android.props = osu.Android.props
osu.iOS.props = osu.iOS.props
CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset
global.json = global.json
osu.sln.DotSettings = osu.sln.DotSettings
osu.TestProject.props = osu.TestProject.props
EndProjectSection
@ -95,6 +94,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{1743BF7C-E6AE-4A06-BAD9-166D62894303}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysis", "{FB156649-D457-4D1A-969C-D3A23FD31513}"
ProjectSection(SolutionItems) = preProject
CodeAnalysis\BannedSymbols.txt = CodeAnalysis\BannedSymbols.txt
CodeAnalysis\osu.globalconfig = CodeAnalysis\osu.globalconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU