mirror of
https://github.com/ppy/osu.git
synced 2025-01-30 03:02:54 +08:00
Merge branch 'master' into new-combo-editor
This commit is contained in:
commit
933f5db208
@ -51,8 +51,11 @@ dotnet_diagnostic.IDE1006.severity = warning
|
|||||||
# Too many noisy warnings for parsing/formatting numbers
|
# Too many noisy warnings for parsing/formatting numbers
|
||||||
dotnet_diagnostic.CA1305.severity = none
|
dotnet_diagnostic.CA1305.severity = none
|
||||||
|
|
||||||
|
# messagepack complains about "osu" not being title cased due to reserved words
|
||||||
|
dotnet_diagnostic.CS8981.severity = none
|
||||||
|
|
||||||
# CA1507: Use nameof to express symbol names
|
# CA1507: Use nameof to express symbol names
|
||||||
# Flaggs serialization name attributes
|
# Flags serialization name attributes
|
||||||
dotnet_diagnostic.CA1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
|
|
||||||
# CA1806: Do not ignore method results
|
# CA1806: Do not ignore method results
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<GenerateProgramFile>false</GenerateProgramFile>
|
<GenerateProgramFile>false</GenerateProgramFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||||
|
@ -17,6 +17,7 @@ namespace osu.Desktop.Windows
|
|||||||
public static class WindowsAssociationManager
|
public static class WindowsAssociationManager
|
||||||
{
|
{
|
||||||
private const string software_classes = @"Software\Classes";
|
private const string software_classes = @"Software\Classes";
|
||||||
|
private const string software_registered_applications = @"Software\RegisteredApplications";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sub key for setting the icon.
|
/// Sub key for setting the icon.
|
||||||
@ -36,7 +37,11 @@ namespace osu.Desktop.Windows
|
|||||||
/// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit,
|
/// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit,
|
||||||
/// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key.
|
/// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string program_id_prefix = "osu.File";
|
private const string program_id_file_prefix = "osu.File";
|
||||||
|
|
||||||
|
private const string program_id_protocol_prefix = "osu.Uri";
|
||||||
|
|
||||||
|
private static readonly ApplicationCapability application_capability = new ApplicationCapability(@"osu", @"Software\ppy\osu\Capabilities", "osu!(lazer)");
|
||||||
|
|
||||||
private static readonly FileAssociation[] file_associations =
|
private static readonly FileAssociation[] file_associations =
|
||||||
{
|
{
|
||||||
@ -56,14 +61,13 @@ namespace osu.Desktop.Windows
|
|||||||
/// Installs file and URI associations.
|
/// Installs file and URI associations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Call <see cref="UpdateDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
/// Call <see cref="LocaliseDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void InstallAssociations()
|
public static void InstallAssociations()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
updateAssociations();
|
updateAssociations();
|
||||||
updateDescriptions(null); // write default descriptions in case `UpdateDescriptions()` is not called.
|
|
||||||
NotifyShellUpdate();
|
NotifyShellUpdate();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -76,17 +80,13 @@ namespace osu.Desktop.Windows
|
|||||||
/// Updates associations with latest definitions.
|
/// Updates associations with latest definitions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Call <see cref="UpdateDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
/// Call <see cref="LocaliseDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static void UpdateAssociations()
|
public static void UpdateAssociations()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
updateAssociations();
|
updateAssociations();
|
||||||
|
|
||||||
// TODO: Remove once UpdateDescriptions() is called as specified in the xmldoc.
|
|
||||||
updateDescriptions(null); // always write default descriptions, in case of updating from an older version in which file associations were not implemented/installed
|
|
||||||
|
|
||||||
NotifyShellUpdate();
|
NotifyShellUpdate();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -95,11 +95,19 @@ namespace osu.Desktop.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateDescriptions(LocalisationManager localisationManager)
|
// TODO: call this sometime.
|
||||||
|
public static void LocaliseDescriptions(LocalisationManager localisationManager)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
updateDescriptions(localisationManager);
|
application_capability.LocaliseDescription(localisationManager);
|
||||||
|
|
||||||
|
foreach (var association in file_associations)
|
||||||
|
association.LocaliseDescription(localisationManager);
|
||||||
|
|
||||||
|
foreach (var association in uri_associations)
|
||||||
|
association.LocaliseDescription(localisationManager);
|
||||||
|
|
||||||
NotifyShellUpdate();
|
NotifyShellUpdate();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@ -112,6 +120,8 @@ namespace osu.Desktop.Windows
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
application_capability.Uninstall();
|
||||||
|
|
||||||
foreach (var association in file_associations)
|
foreach (var association in file_associations)
|
||||||
association.Uninstall();
|
association.Uninstall();
|
||||||
|
|
||||||
@ -133,22 +143,16 @@ namespace osu.Desktop.Windows
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static void updateAssociations()
|
private static void updateAssociations()
|
||||||
{
|
{
|
||||||
|
application_capability.Install();
|
||||||
|
|
||||||
foreach (var association in file_associations)
|
foreach (var association in file_associations)
|
||||||
association.Install();
|
association.Install();
|
||||||
|
|
||||||
foreach (var association in uri_associations)
|
foreach (var association in uri_associations)
|
||||||
association.Install();
|
association.Install();
|
||||||
}
|
|
||||||
|
|
||||||
private static void updateDescriptions(LocalisationManager? localisation)
|
application_capability.RegisterFileAssociations(file_associations);
|
||||||
{
|
application_capability.RegisterUriAssociations(uri_associations);
|
||||||
foreach (var association in file_associations)
|
|
||||||
association.UpdateDescription(getLocalisedString(association.Description));
|
|
||||||
|
|
||||||
foreach (var association in uri_associations)
|
|
||||||
association.UpdateDescription(getLocalisedString(association.Description));
|
|
||||||
|
|
||||||
string getLocalisedString(LocalisableString s) => localisation?.GetLocalisedString(s) ?? s.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Native interop
|
#region Native interop
|
||||||
@ -174,9 +178,87 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private record FileAssociation(string Extension, LocalisableString Description, string IconPath)
|
private class ApplicationCapability
|
||||||
{
|
{
|
||||||
private string programId => $@"{program_id_prefix}{Extension}";
|
private string uniqueName { get; }
|
||||||
|
private string capabilityPath { get; }
|
||||||
|
private LocalisableString description { get; }
|
||||||
|
|
||||||
|
public ApplicationCapability(string uniqueName, string capabilityPath, LocalisableString description)
|
||||||
|
{
|
||||||
|
this.uniqueName = uniqueName;
|
||||||
|
this.capabilityPath = capabilityPath;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers an application capability according to <see href="https://learn.microsoft.com/en-us/windows/win32/shell/default-programs#registering-an-application-for-use-with-default-programs">
|
||||||
|
/// Registering an Application for Use with Default Programs</see>.
|
||||||
|
/// </summary>
|
||||||
|
public void Install()
|
||||||
|
{
|
||||||
|
using (var capability = Registry.CurrentUser.CreateSubKey(capabilityPath))
|
||||||
|
{
|
||||||
|
capability.SetValue(@"ApplicationDescription", description.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var registeredApplications = Registry.CurrentUser.OpenSubKey(software_registered_applications, true))
|
||||||
|
registeredApplications?.SetValue(uniqueName, capabilityPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterFileAssociations(FileAssociation[] associations)
|
||||||
|
{
|
||||||
|
using var capability = Registry.CurrentUser.OpenSubKey(capabilityPath, true);
|
||||||
|
if (capability == null) return;
|
||||||
|
|
||||||
|
using var fileAssociations = capability.CreateSubKey(@"FileAssociations");
|
||||||
|
|
||||||
|
foreach (var association in associations)
|
||||||
|
fileAssociations.SetValue(association.Extension, association.ProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterUriAssociations(UriAssociation[] associations)
|
||||||
|
{
|
||||||
|
using var capability = Registry.CurrentUser.OpenSubKey(capabilityPath, true);
|
||||||
|
if (capability == null) return;
|
||||||
|
|
||||||
|
using var urlAssociations = capability.CreateSubKey(@"UrlAssociations");
|
||||||
|
|
||||||
|
foreach (var association in associations)
|
||||||
|
urlAssociations.SetValue(association.Protocol, association.ProgramId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LocaliseDescription(LocalisationManager localisationManager)
|
||||||
|
{
|
||||||
|
using (var capability = Registry.CurrentUser.OpenSubKey(capabilityPath, true))
|
||||||
|
{
|
||||||
|
capability?.SetValue(@"ApplicationDescription", localisationManager.GetLocalisedString(description));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Uninstall()
|
||||||
|
{
|
||||||
|
using (var registeredApplications = Registry.CurrentUser.OpenSubKey(software_registered_applications, true))
|
||||||
|
registeredApplications?.DeleteValue(uniqueName, throwOnMissingValue: false);
|
||||||
|
|
||||||
|
Registry.CurrentUser.DeleteSubKeyTree(capabilityPath, throwOnMissingSubKey: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FileAssociation
|
||||||
|
{
|
||||||
|
public string ProgramId => $@"{program_id_file_prefix}{Extension}";
|
||||||
|
|
||||||
|
public string Extension { get; }
|
||||||
|
private LocalisableString description { get; }
|
||||||
|
private string iconPath { get; }
|
||||||
|
|
||||||
|
public FileAssociation(string extension, LocalisableString description, string iconPath)
|
||||||
|
{
|
||||||
|
Extension = extension;
|
||||||
|
this.description = description;
|
||||||
|
this.iconPath = iconPath;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Installs a file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
/// Installs a file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
||||||
@ -187,10 +269,12 @@ namespace osu.Desktop.Windows
|
|||||||
if (classes == null) return;
|
if (classes == null) return;
|
||||||
|
|
||||||
// register a program id for the given extension
|
// register a program id for the given extension
|
||||||
using (var programKey = classes.CreateSubKey(programId))
|
using (var programKey = classes.CreateSubKey(ProgramId))
|
||||||
{
|
{
|
||||||
|
programKey.SetValue(null, description.ToString());
|
||||||
|
|
||||||
using (var defaultIconKey = programKey.CreateSubKey(default_icon))
|
using (var defaultIconKey = programKey.CreateSubKey(default_icon))
|
||||||
defaultIconKey.SetValue(null, IconPath);
|
defaultIconKey.SetValue(null, iconPath);
|
||||||
|
|
||||||
using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
||||||
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
||||||
@ -198,23 +282,25 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
using (var extensionKey = classes.CreateSubKey(Extension))
|
using (var extensionKey = classes.CreateSubKey(Extension))
|
||||||
{
|
{
|
||||||
// set ourselves as the default program
|
// Clear out our existing default ProgramID. Default programs in Windows are handled internally by Explorer,
|
||||||
extensionKey.SetValue(null, programId);
|
// so having it here is just confusing and may override user preferences.
|
||||||
|
if (extensionKey.GetValue(null) is string s && s == ProgramId)
|
||||||
|
extensionKey.SetValue(null, string.Empty);
|
||||||
|
|
||||||
// add to the open with dialog
|
// add to the open with dialog
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/shell/how-to-include-an-application-on-the-open-with-dialog-box
|
// https://learn.microsoft.com/en-us/windows/win32/shell/how-to-include-an-application-on-the-open-with-dialog-box
|
||||||
using (var openWithKey = extensionKey.CreateSubKey(@"OpenWithProgIds"))
|
using (var openWithKey = extensionKey.CreateSubKey(@"OpenWithProgIds"))
|
||||||
openWithKey.SetValue(programId, string.Empty);
|
openWithKey.SetValue(ProgramId, string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDescription(string description)
|
public void LocaliseDescription(LocalisationManager localisationManager)
|
||||||
{
|
{
|
||||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||||
if (classes == null) return;
|
if (classes == null) return;
|
||||||
|
|
||||||
using (var programKey = classes.OpenSubKey(programId, true))
|
using (var programKey = classes.OpenSubKey(ProgramId, true))
|
||||||
programKey?.SetValue(null, description);
|
programKey?.SetValue(null, localisationManager.GetLocalisedString(description));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -227,26 +313,34 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
using (var extensionKey = classes.OpenSubKey(Extension, true))
|
using (var extensionKey = classes.OpenSubKey(Extension, true))
|
||||||
{
|
{
|
||||||
// clear our default association so that Explorer doesn't show the raw programId to users
|
|
||||||
// the null/(Default) entry is used for both ProdID association and as a fallback friendly name, for legacy reasons
|
|
||||||
if (extensionKey?.GetValue(null) is string s && s == programId)
|
|
||||||
extensionKey.SetValue(null, string.Empty);
|
|
||||||
|
|
||||||
using (var openWithKey = extensionKey?.CreateSubKey(@"OpenWithProgIds"))
|
using (var openWithKey = extensionKey?.CreateSubKey(@"OpenWithProgIds"))
|
||||||
openWithKey?.DeleteValue(programId, throwOnMissingValue: false);
|
openWithKey?.DeleteValue(ProgramId, throwOnMissingValue: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
classes.DeleteSubKeyTree(programId, throwOnMissingSubKey: false);
|
classes.DeleteSubKeyTree(ProgramId, throwOnMissingSubKey: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private record UriAssociation(string Protocol, LocalisableString Description, string IconPath)
|
private class UriAssociation
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The <c>URL Protocol</c> string value indicates that this key declares a custom pluggable protocol handler."
|
/// "The <c>URL Protocol</c> string value indicates that this key declares a custom pluggable protocol handler."
|
||||||
/// See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
/// See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string URL_PROTOCOL = @"URL Protocol";
|
private const string url_protocol = @"URL Protocol";
|
||||||
|
|
||||||
|
public string Protocol { get; }
|
||||||
|
private LocalisableString description { get; }
|
||||||
|
private string iconPath { get; }
|
||||||
|
|
||||||
|
public UriAssociation(string protocol, LocalisableString description, string iconPath)
|
||||||
|
{
|
||||||
|
Protocol = protocol;
|
||||||
|
this.description = description;
|
||||||
|
this.iconPath = iconPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ProgramId => $@"{program_id_protocol_prefix}.{Protocol}";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
/// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
||||||
@ -258,29 +352,38 @@ namespace osu.Desktop.Windows
|
|||||||
|
|
||||||
using (var protocolKey = classes.CreateSubKey(Protocol))
|
using (var protocolKey = classes.CreateSubKey(Protocol))
|
||||||
{
|
{
|
||||||
protocolKey.SetValue(URL_PROTOCOL, string.Empty);
|
protocolKey.SetValue(null, $@"URL:{description}");
|
||||||
|
protocolKey.SetValue(url_protocol, string.Empty);
|
||||||
|
|
||||||
using (var defaultIconKey = protocolKey.CreateSubKey(default_icon))
|
// clear out old data
|
||||||
defaultIconKey.SetValue(null, IconPath);
|
protocolKey.DeleteSubKeyTree(default_icon, throwOnMissingSubKey: false);
|
||||||
|
protocolKey.DeleteSubKeyTree(@"Shell", throwOnMissingSubKey: false);
|
||||||
|
}
|
||||||
|
|
||||||
using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
// register a program id for the given protocol
|
||||||
|
using (var programKey = classes.CreateSubKey(ProgramId))
|
||||||
|
{
|
||||||
|
using (var defaultIconKey = programKey.CreateSubKey(default_icon))
|
||||||
|
defaultIconKey.SetValue(null, iconPath);
|
||||||
|
|
||||||
|
using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
||||||
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDescription(string description)
|
public void LocaliseDescription(LocalisationManager localisationManager)
|
||||||
{
|
{
|
||||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||||
if (classes == null) return;
|
if (classes == null) return;
|
||||||
|
|
||||||
using (var protocolKey = classes.OpenSubKey(Protocol, true))
|
using (var protocolKey = classes.OpenSubKey(Protocol, true))
|
||||||
protocolKey?.SetValue(null, $@"URL:{description}");
|
protocolKey?.SetValue(null, $@"URL:{localisationManager.GetLocalisedString(description)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Uninstall()
|
public void Uninstall()
|
||||||
{
|
{
|
||||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||||
classes?.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false);
|
classes?.DeleteSubKeyTree(ProgramId, throwOnMissingSubKey: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="System.IO.Packaging" Version="8.0.1" />
|
<PackageReference Include="System.IO.Packaging" Version="9.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageReference Include="Velopack" Version="0.0.915" />
|
<PackageReference Include="Velopack" Version="0.0.1053" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||||
<PackageReference Include="nunit" Version="3.14.0" />
|
<PackageReference Include="nunit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
{
|
{
|
||||||
public partial class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene
|
public partial class TestSceneJuiceStreamSelectionBlueprint : CatchSelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
private JuiceStream hitObject;
|
private JuiceStream hitObject = null!;
|
||||||
|
|
||||||
private readonly ManualClock manualClock = new ManualClock();
|
private readonly ManualClock manualClock = new ManualClock();
|
||||||
|
|
||||||
@ -193,6 +191,17 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
addVertexCheckStep(1, 0, times[0], positions[0]);
|
addVertexCheckStep(1, 0, times[0], positions[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDeletingSecondVertexDeletesEntireJuiceStream()
|
||||||
|
{
|
||||||
|
double[] times = { 100, 400 };
|
||||||
|
float[] positions = { 100, 150 };
|
||||||
|
addBlueprintStep(times, positions);
|
||||||
|
|
||||||
|
addDeleteVertexSteps(times[1], positions[1]);
|
||||||
|
AddAssert("juice stream deleted", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestVertexResampling()
|
public void TestVertexResampling()
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateHitObjectFromPath(JuiceStream hitObject)
|
public virtual void UpdateHitObjectFromPath(JuiceStream hitObject)
|
||||||
{
|
{
|
||||||
// The SV setting may need to be changed for the current path.
|
// The SV setting may need to be changed for the current path.
|
||||||
var svBindable = hitObject.SliderVelocityMultiplierBindable;
|
var svBindable = hitObject.SliderVelocityMultiplierBindable;
|
||||||
|
@ -138,5 +138,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
|
|
||||||
EditorBeatmap?.EndChange();
|
EditorBeatmap?.EndChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateHitObjectFromPath(JuiceStream hitObject)
|
||||||
|
{
|
||||||
|
base.UpdateHitObjectFromPath(hitObject);
|
||||||
|
|
||||||
|
if (hitObject.Path.ControlPoints.Count <= 1 || !hitObject.Path.HasValidLength)
|
||||||
|
EditorBeatmap?.Remove(hitObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,10 +88,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
switch (PlacementActive)
|
switch (PlacementActive)
|
||||||
{
|
{
|
||||||
case PlacementState.Waiting:
|
case PlacementState.Waiting:
|
||||||
if (!(result.Time is double snappedTime)) return;
|
|
||||||
|
|
||||||
HitObject.OriginalX = ToLocalSpace(result.ScreenSpacePosition).X;
|
HitObject.OriginalX = ToLocalSpace(result.ScreenSpacePosition).X;
|
||||||
HitObject.StartTime = snappedTime;
|
if (result.Time is double snappedTime)
|
||||||
|
HitObject.StartTime = snappedTime;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlacementState.Active:
|
case PlacementState.Active:
|
||||||
@ -107,21 +106,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
|
|||||||
Vector2 startPosition = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
Vector2 startPosition = CatchHitObjectUtils.GetStartPosition(HitObjectContainer, HitObject);
|
||||||
editablePath.Position = nestedOutlineContainer.Position = scrollingPath.Position = startPosition;
|
editablePath.Position = nestedOutlineContainer.Position = scrollingPath.Position = startPosition;
|
||||||
|
|
||||||
updateHitObjectFromPath();
|
if (lastEditablePathId != editablePath.PathId)
|
||||||
}
|
editablePath.UpdateHitObjectFromPath(HitObject);
|
||||||
|
lastEditablePathId = editablePath.PathId;
|
||||||
|
|
||||||
private void updateHitObjectFromPath()
|
|
||||||
{
|
|
||||||
if (lastEditablePathId == editablePath.PathId)
|
|
||||||
return;
|
|
||||||
|
|
||||||
editablePath.UpdateHitObjectFromPath(HitObject);
|
|
||||||
ApplyDefaultsToHitObject();
|
ApplyDefaultsToHitObject();
|
||||||
|
|
||||||
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
scrollingPath.UpdatePathFrom(HitObjectContainer, HitObject);
|
||||||
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
nestedOutlineContainer.UpdateNestedObjectsFrom(HitObjectContainer, HitObject);
|
||||||
|
|
||||||
lastEditablePathId = editablePath.PathId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double positionToTime(float relativeYPosition)
|
private double positionToTime(float relativeYPosition)
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
|
|
||||||
protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider);
|
protected override Drawable CreateHitObjectInspector() => new CatchHitObjectInspector(DistanceSnapProvider);
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
protected override IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
|
||||||
=> base.CreateTernaryButtons()
|
=> base.CreateTernaryButtons()
|
||||||
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="Moq" Version="4.18.4" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -626,7 +626,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||||
{
|
{
|
||||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
|
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && DrawableObject.Body.Alpha > 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (ControlPointVisualiser == null)
|
if (ControlPointVisualiser == null)
|
||||||
|
@ -53,9 +53,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
|
protected override Drawable CreateHitObjectInspector() => new OsuHitObjectInspector();
|
||||||
|
|
||||||
protected override IEnumerable<TernaryButton> CreateTernaryButtons()
|
protected override IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
|
||||||
=> base.CreateTernaryButtons()
|
=> base.CreateTernaryButtons()
|
||||||
.Append(new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }))
|
.Append(new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = rectangularGridSnapToggle,
|
||||||
|
Description = "Grid Snap",
|
||||||
|
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap },
|
||||||
|
})
|
||||||
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
.Concat(DistanceSnapProvider.CreateTernaryButtons());
|
||||||
|
|
||||||
private BindableList<HitObject> selectedHitObjects;
|
private BindableList<HitObject> selectedHitObjects;
|
||||||
|
@ -382,6 +382,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
repeat.SuppressHitAnimations();
|
repeat.SuppressHitAnimations();
|
||||||
|
|
||||||
TailCircle.SuppressHitAnimations();
|
TailCircle.SuppressHitAnimations();
|
||||||
|
|
||||||
|
// This method is called every frame in editor contexts, thus the lack of need for transforms.
|
||||||
|
|
||||||
|
if (Time.Current >= HitStateUpdateTime)
|
||||||
|
{
|
||||||
|
// Apply the slider's alpha to *only* the body.
|
||||||
|
// This allows start and – more importantly – end circles to fade slower than the overall slider.
|
||||||
|
if (Alpha < 1)
|
||||||
|
Body.Alpha = Alpha;
|
||||||
|
Alpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
LifetimeEnd = HitStateUpdateTime + 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RestoreHitAnimations()
|
internal void RestoreHitAnimations()
|
||||||
|
@ -5,12 +5,12 @@ using System;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -75,44 +75,38 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
|||||||
|
|
||||||
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
accentColour.BindValueChanged(accent => icon.Colour = accent.NewValue.Darken(4), true);
|
||||||
|
|
||||||
drawableRepeat.ApplyCustomUpdateState += updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
protected override void Update()
|
||||||
{
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (Time.Current >= drawableRepeat.HitStateUpdateTime && drawableRepeat.State.Value == ArmedState.Hit)
|
||||||
|
{
|
||||||
|
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||||
|
Scale = new Vector2(Interpolation.ValueAt(Time.Current, 1, 1.5f, drawableRepeat.HitStateUpdateTime, drawableRepeat.HitStateUpdateTime + animDuration, Easing.Out));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Scale = Vector2.One;
|
||||||
|
|
||||||
const float move_distance = -12;
|
const float move_distance = -12;
|
||||||
|
const float scale_amount = 1.3f;
|
||||||
|
|
||||||
const double move_out_duration = 35;
|
const double move_out_duration = 35;
|
||||||
const double move_in_duration = 250;
|
const double move_in_duration = 250;
|
||||||
const double total = 300;
|
const double total = 300;
|
||||||
|
|
||||||
switch (state)
|
double loopCurrentTime = (Time.Current - drawableRepeat.AnimationStartTime.Value) % total;
|
||||||
{
|
|
||||||
case ArmedState.Idle:
|
|
||||||
main.ScaleTo(1.3f, move_out_duration, Easing.Out)
|
|
||||||
.Then()
|
|
||||||
.ScaleTo(1f, move_in_duration, Easing.Out)
|
|
||||||
.Loop(total - (move_in_duration + move_out_duration));
|
|
||||||
side
|
|
||||||
.MoveToX(move_distance, move_out_duration, Easing.Out)
|
|
||||||
.Then()
|
|
||||||
.MoveToX(0, move_in_duration, Easing.Out)
|
|
||||||
.Loop(total - (move_in_duration + move_out_duration));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArmedState.Hit:
|
if (loopCurrentTime < move_out_duration)
|
||||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
main.Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, 1, scale_amount, 0, move_out_duration, Easing.Out));
|
||||||
this.ScaleTo(1.5f, animDuration, Easing.Out);
|
else
|
||||||
break;
|
main.Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, scale_amount, 1f, move_out_duration, move_out_duration + move_in_duration, Easing.Out));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
if (loopCurrentTime < move_out_duration)
|
||||||
{
|
side.X = Interpolation.ValueAt(loopCurrentTime, 0, move_distance, 0, move_out_duration, Easing.Out);
|
||||||
base.Dispose(isDisposing);
|
else
|
||||||
|
side.X = Interpolation.ValueAt(loopCurrentTime, move_distance, 0, move_out_duration, move_out_duration + move_in_duration, Easing.Out);
|
||||||
if (drawableRepeat.IsNotNull())
|
|
||||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -40,37 +40,31 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
private void load(DrawableHitObject drawableObject)
|
private void load(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
drawableRepeat = (DrawableSliderRepeat)drawableObject;
|
||||||
drawableRepeat.ApplyCustomUpdateState += updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
protected override void Update()
|
||||||
{
|
{
|
||||||
const double move_out_duration = 35;
|
base.Update();
|
||||||
const double move_in_duration = 250;
|
|
||||||
const double total = 300;
|
|
||||||
|
|
||||||
switch (state)
|
if (Time.Current >= drawableRepeat.HitStateUpdateTime && drawableRepeat.State.Value == ArmedState.Hit)
|
||||||
{
|
{
|
||||||
case ArmedState.Idle:
|
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||||
InternalChild.ScaleTo(1.3f, move_out_duration, Easing.Out)
|
Scale = new Vector2(Interpolation.ValueAt(Time.Current, 1, 1.5f, drawableRepeat.HitStateUpdateTime, drawableRepeat.HitStateUpdateTime + animDuration, Easing.Out));
|
||||||
.Then()
|
|
||||||
.ScaleTo(1f, move_in_duration, Easing.Out)
|
|
||||||
.Loop(total - (move_in_duration + move_out_duration));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ArmedState.Hit:
|
|
||||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
|
||||||
InternalChild.ScaleTo(1.5f, animDuration, Easing.Out);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
const float scale_amount = 1.3f;
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
const double move_out_duration = 35;
|
||||||
{
|
const double move_in_duration = 250;
|
||||||
base.Dispose(isDisposing);
|
const double total = 300;
|
||||||
|
|
||||||
if (drawableRepeat.IsNotNull())
|
double loopCurrentTime = (Time.Current - drawableRepeat.AnimationStartTime.Value) % total;
|
||||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
if (loopCurrentTime < move_out_duration)
|
||||||
|
Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, 1, scale_amount, 0, move_out_duration, Easing.Out));
|
||||||
|
else
|
||||||
|
Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, scale_amount, 1f, move_out_duration, move_out_duration + move_in_duration, Easing.Out));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,12 @@ using osu.Framework.Extensions.ObjectExtensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -51,8 +53,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
textureIsDefaultSkin = skin is ISkinTransformer transformer && transformer.Skin is DefaultLegacySkin;
|
||||||
|
|
||||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
|
||||||
|
|
||||||
shouldRotate = skinSource.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value <= 1;
|
shouldRotate = skinSource.GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version)?.Value <= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
accentColour = drawableRepeat.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(c =>
|
accentColour.BindValueChanged(c =>
|
||||||
{
|
{
|
||||||
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > (600 / 255f) ? Color4.Black : Color4.White;
|
arrow.Colour = textureIsDefaultSkin && c.NewValue.R + c.NewValue.G + c.NewValue.B > 600 / 255f ? Color4.Black : Color4.White;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,36 +80,32 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
drawableRepeat.DrawableSlider.OverlayElementContainer.Add(proxy);
|
drawableRepeat.DrawableSlider.OverlayElementContainer.Add(proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStateTransforms(DrawableHitObject hitObject, ArmedState state)
|
protected override void Update()
|
||||||
{
|
{
|
||||||
const double duration = 300;
|
base.Update();
|
||||||
const float rotation = 5.625f;
|
|
||||||
|
|
||||||
switch (state)
|
if (Time.Current >= drawableRepeat.HitStateUpdateTime && drawableRepeat.State.Value == ArmedState.Hit)
|
||||||
{
|
{
|
||||||
case ArmedState.Idle:
|
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||||
if (shouldRotate)
|
arrow.Scale = new Vector2(Interpolation.ValueAt(Time.Current, 1, 1.4f, drawableRepeat.HitStateUpdateTime, drawableRepeat.HitStateUpdateTime + animDuration, Easing.Out));
|
||||||
{
|
}
|
||||||
InternalChild.ScaleTo(1.3f)
|
else
|
||||||
.RotateTo(rotation)
|
{
|
||||||
.Then()
|
const double duration = 300;
|
||||||
.ScaleTo(1f, duration)
|
const float rotation = 5.625f;
|
||||||
.RotateTo(-rotation, duration)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
InternalChild.ScaleTo(1.3f).Then()
|
|
||||||
.ScaleTo(1f, duration, Easing.Out)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
double loopCurrentTime = (Time.Current - drawableRepeat.AnimationStartTime.Value) % duration;
|
||||||
|
|
||||||
case ArmedState.Hit:
|
// Reference: https://github.com/peppy/osu-stable-reference/blob/2280c4c436f80d04f9c79d3c905db00ac2902273/osu!/GameplayElements/HitObjects/Osu/HitCircleSliderEnd.cs#L79-L96
|
||||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
if (shouldRotate)
|
||||||
InternalChild.ScaleTo(1.4f, animDuration, Easing.Out);
|
{
|
||||||
break;
|
arrow.Rotation = Interpolation.ValueAt(loopCurrentTime, rotation, -rotation, 0, duration);
|
||||||
|
arrow.Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, 1.3f, 1, 0, duration));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
arrow.Scale = new Vector2(Interpolation.ValueAt(loopCurrentTime, 1.3f, 1, 0, duration, Easing.Out));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +116,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
if (drawableRepeat.IsNotNull())
|
if (drawableRepeat.IsNotNull())
|
||||||
{
|
{
|
||||||
drawableRepeat.HitObjectApplied -= onHitObjectApplied;
|
drawableRepeat.HitObjectApplied -= onHitObjectApplied;
|
||||||
drawableRepeat.ApplyCustomUpdateState -= updateStateTransforms;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -539,5 +539,85 @@ namespace osu.Game.Tests.Editing
|
|||||||
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX));
|
Assert.That(beatmap.Breaks[0].EndTime, Is.EqualTo(5000 - OsuHitObject.PREEMPT_MAX));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPuttingObjectBetweenBreakEndAndAnotherObjectForcesNewCombo()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new EditorBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||||
|
Difficulty =
|
||||||
|
{
|
||||||
|
ApproachRate = 10,
|
||||||
|
},
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||||
|
new HitCircle { StartTime = 4500 },
|
||||||
|
new HitCircle { StartTime = 5000, NewCombo = true },
|
||||||
|
},
|
||||||
|
Breaks =
|
||||||
|
{
|
||||||
|
new BreakPeriod(2000, 4000),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var ho in beatmap.HitObjects)
|
||||||
|
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[1]).NewCombo, Is.True);
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[2]).NewCombo, Is.True);
|
||||||
|
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[0]).ComboIndex, Is.EqualTo(1));
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[1]).ComboIndex, Is.EqualTo(2));
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[2]).ComboIndex, Is.EqualTo(3));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAutomaticallyInsertedBreakForcesNewCombo()
|
||||||
|
{
|
||||||
|
var controlPoints = new ControlPointInfo();
|
||||||
|
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
var beatmap = new EditorBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPoints,
|
||||||
|
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||||
|
Difficulty =
|
||||||
|
{
|
||||||
|
ApproachRate = 10,
|
||||||
|
},
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000, NewCombo = true },
|
||||||
|
new HitCircle { StartTime = 5000 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var ho in beatmap.HitObjects)
|
||||||
|
ho.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
|
||||||
|
|
||||||
|
var beatmapProcessor = new EditorBeatmapProcessor(beatmap, new OsuRuleset());
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(beatmap.Breaks, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[1]).NewCombo, Is.True);
|
||||||
|
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[0]).ComboIndex, Is.EqualTo(1));
|
||||||
|
Assert.That(((HitCircle)beatmap.HitObjects[1]).ComboIndex, Is.EqualTo(2));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,18 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Backgrounds;
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Components;
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
using osu.Game.Screens.Edit.GameplayTest;
|
using osu.Game.Screens.Edit.GameplayTest;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -42,14 +42,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
private BeatmapSetInfo importedBeatmapSet;
|
private BeatmapSetInfo importedBeatmapSet;
|
||||||
|
|
||||||
private Bindable<float> editorDim;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
|
||||||
editorDim = config.GetBindable<float>(OsuSetting.EditorDim);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely());
|
AddStep("import test beatmap", () => importedBeatmapSet = BeatmapImportHelper.LoadOszIntoOsu(game).GetResultSafely());
|
||||||
@ -80,15 +72,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
AddStep("exit player", () => editorPlayer.Exit());
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
AddUntilStep("background has correct params", () =>
|
AddUntilStep("background is correct", () => this.ChildrenOfType<BackgroundScreenStack>().Single().CurrentScreen is EditorBackgroundScreen);
|
||||||
{
|
|
||||||
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
|
|
||||||
// due to the beatmap refetch logic ran on editor suspend.
|
|
||||||
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
|
|
||||||
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
|
|
||||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
|
|
||||||
return background.DimWhenUserSettingsIgnored.Value == editorDim.Value && background.BlurAmount.Value == 0;
|
|
||||||
});
|
|
||||||
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,20 +97,41 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("exit player", () => editorPlayer.Exit());
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
AddUntilStep("background has correct params", () =>
|
AddUntilStep("background is correct", () => this.ChildrenOfType<BackgroundScreenStack>().Single().CurrentScreen is EditorBackgroundScreen);
|
||||||
{
|
|
||||||
// the test gameplay player's beatmap may be the "same" beatmap as the one being edited, *but* the `BeatmapInfo` references may differ
|
|
||||||
// due to the beatmap refetch logic ran on editor suspend.
|
|
||||||
// this test cares about checking the background belonging to the editor specifically, so check that using reference equality
|
|
||||||
// (as `.Equals()` cannot discern between the two, as they technically share the same database GUID).
|
|
||||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single(b => ReferenceEquals(b.Beatmap.BeatmapInfo, EditorBeatmap.BeatmapInfo));
|
|
||||||
return background.DimWhenUserSettingsIgnored.Value == editorDim.Value && background.BlurAmount.Value == 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep("start track", () => EditorClock.Start());
|
AddStep("start track", () => EditorClock.Start());
|
||||||
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
AddAssert("sample playback re-enabled", () => !Editor.SamplePlaybackDisabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGameplayTestResetsPlaybackSpeedAdjustment()
|
||||||
|
{
|
||||||
|
AddStep("start track", () => EditorClock.Start());
|
||||||
|
AddStep("change playback speed", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<PlaybackControl.PlaybackTabControl.PlaybackTabItem>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddAssert("track playback rate is 0.25x", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(0.25));
|
||||||
|
|
||||||
|
AddStep("click test gameplay button", () =>
|
||||||
|
{
|
||||||
|
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(button);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
EditorPlayer editorPlayer = null;
|
||||||
|
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
|
||||||
|
AddAssert("editor track stopped", () => !EditorClock.IsRunning);
|
||||||
|
AddAssert("track playback rate is 1x", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("exit player", () => editorPlayer.Exit());
|
||||||
|
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
|
||||||
|
AddAssert("track playback rate is 0.25x", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(0.25));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(2000)] // chosen to be after last object in the map
|
[TestCase(2000)] // chosen to be after last object in the map
|
||||||
[TestCase(22000)] // chosen to be in the middle of the last spinner
|
[TestCase(22000)] // chosen to be in the middle of the last spinner
|
||||||
public void TestGameplayTestAtEndOfBeatmap(int offsetFromEnd)
|
public void TestGameplayTestAtEndOfBeatmap(int offsetFromEnd)
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Input.StateChanges;
|
using osu.Framework.Input.StateChanges;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -42,6 +43,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private GameplayState gameplayState;
|
private GameplayState gameplayState;
|
||||||
|
|
||||||
|
private Drawable content;
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUpSteps()
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
@ -58,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
|
CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) },
|
||||||
Child = createContent(),
|
Child = content = createContent(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,10 +70,32 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
public void TestBasic()
|
public void TestBasic()
|
||||||
{
|
{
|
||||||
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
|
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
|
||||||
AddUntilStep("at least one frame recorded", () => replay.Frames.Count > 0);
|
AddUntilStep("at least one frame recorded", () => replay.Frames.Count, () => Is.GreaterThanOrEqualTo(0));
|
||||||
AddUntilStep("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
|
AddUntilStep("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Explicit("Making this test work in a headless context is high effort due to rate adjustment requirements not aligning with the global fast clock. StopwatchClock usage would need to be replace with a rate adjusting clock that still reads from the parent clock. High effort for a test which likely will not see any changes to covered code for some years.")]
|
||||||
|
public void TestSlowClockStillRecordsFramesInRealtime()
|
||||||
|
{
|
||||||
|
ScheduledDelegate moveFunction = null;
|
||||||
|
|
||||||
|
AddStep("set slow running clock", () =>
|
||||||
|
{
|
||||||
|
var stopwatchClock = new StopwatchClock(true) { Rate = 0.01 };
|
||||||
|
stopwatchClock.Seek(Clock.CurrentTime);
|
||||||
|
|
||||||
|
content.Clock = new FramedClock(stopwatchClock);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
|
||||||
|
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
|
||||||
|
AddWaitStep("move", 10);
|
||||||
|
AddStep("stop move", () => moveFunction.Cancel());
|
||||||
|
AddAssert("at least 60 frames recorded", () => replay.Frames.Count, () => Is.GreaterThanOrEqualTo(60));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHighFrameRate()
|
public void TestHighFrameRate()
|
||||||
{
|
{
|
||||||
@ -81,7 +106,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
|
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
|
||||||
AddWaitStep("move", 10);
|
AddWaitStep("move", 10);
|
||||||
AddStep("stop move", () => moveFunction.Cancel());
|
AddStep("stop move", () => moveFunction.Cancel());
|
||||||
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
|
AddAssert("at least 60 frames recorded", () => replay.Frames.Count, () => Is.GreaterThanOrEqualTo(60));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -97,7 +122,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
|
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
|
||||||
AddWaitStep("move", 10);
|
AddWaitStep("move", 10);
|
||||||
AddStep("stop move", () => moveFunction.Cancel());
|
AddStep("stop move", () => moveFunction.Cancel());
|
||||||
AddAssert("less than 10 frames recorded", () => replay.Frames.Count - initialFrameCount < 10);
|
AddAssert("less than 10 frames recorded", () => replay.Frames.Count - initialFrameCount, () => Is.LessThan(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -114,7 +139,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}, 10, true));
|
}, 10, true));
|
||||||
AddWaitStep("move", 10);
|
AddWaitStep("move", 10);
|
||||||
AddStep("stop move", () => moveFunction.Cancel());
|
AddStep("stop move", () => moveFunction.Cancel());
|
||||||
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
|
AddAssert("at least 60 frames recorded", () => replay.Frames.Count, () => Is.GreaterThanOrEqualTo(60));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -3,29 +3,71 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osu.Game.Screens.OnlinePlay.Components;
|
using osu.Game.Screens.OnlinePlay.Components;
|
||||||
using osu.Game.Tests.Visual.OnlinePlay;
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
public partial class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene
|
public partial class TestSceneStarRatingRangeDisplay : OsuTestScene
|
||||||
{
|
{
|
||||||
public override void SetUpSteps()
|
private readonly Room room = new Room();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.LoadComplete();
|
||||||
|
|
||||||
AddStep("create display", () =>
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
SelectedRoom.Value = new Room();
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
Child = new StarRatingRangeDisplay(SelectedRoom.Value)
|
Spacing = new Vector2(10),
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
new StarRatingRangeDisplay(room)
|
||||||
Origin = Anchor.Centre
|
{
|
||||||
};
|
Anchor = Anchor.Centre,
|
||||||
});
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(5),
|
||||||
|
},
|
||||||
|
new StarRatingRangeDisplay(room)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
},
|
||||||
|
new StarRatingRangeDisplay(room)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(1),
|
||||||
|
},
|
||||||
|
new StarRatingRangeDisplay(room)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
Scale = new Vector2(5),
|
||||||
|
},
|
||||||
|
new StarRatingRangeDisplay(room)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
Scale = new Vector2(2),
|
||||||
|
},
|
||||||
|
new StarRatingRangeDisplay(room)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
Scale = new Vector2(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -33,10 +75,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
AddStep("set playlist", () =>
|
AddStep("set playlist", () =>
|
||||||
{
|
{
|
||||||
SelectedRoom.Value!.Playlist =
|
room.Playlist =
|
||||||
[
|
[
|
||||||
new PlaylistItem(new BeatmapInfo { StarRating = min }),
|
new PlaylistItem(new BeatmapInfo { StarRating = min }) { ID = TestResources.GetNextTestID() },
|
||||||
new PlaylistItem(new BeatmapInfo { StarRating = max }),
|
new PlaylistItem(new BeatmapInfo { StarRating = max }) { ID = TestResources.GetNextTestID() },
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
@ -85,6 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestPresentedBeatmapIsRecommended()
|
public void TestPresentedBeatmapIsRecommended()
|
||||||
{
|
{
|
||||||
List<BeatmapSetInfo> beatmapSets = null;
|
List<BeatmapSetInfo> beatmapSets = null;
|
||||||
@ -106,6 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestCurrentRulesetIsRecommended()
|
public void TestCurrentRulesetIsRecommended()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo catchSet = null, mixedSet = null;
|
BeatmapSetInfo catchSet = null, mixedSet = null;
|
||||||
@ -142,6 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestSecondBestRulesetIsRecommended()
|
public void TestSecondBestRulesetIsRecommended()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo osuSet = null, mixedSet = null;
|
BeatmapSetInfo osuSet = null, mixedSet = null;
|
||||||
@ -159,6 +161,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestCorrectStarRatingIsUsed()
|
public void TestCorrectStarRatingIsUsed()
|
||||||
{
|
{
|
||||||
BeatmapSetInfo osuSet = null, maniaSet = null;
|
BeatmapSetInfo osuSet = null, maniaSet = null;
|
||||||
@ -176,6 +179,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestBeatmapListingFilter()
|
public void TestBeatmapListingFilter()
|
||||||
{
|
{
|
||||||
AddStep("set playmode to taiko", () => ((DummyAPIAccess)API).LocalUser.Value.PlayMode = "taiko");
|
AddStep("set playmode to taiko", () => ((DummyAPIAccess)API).LocalUser.Value.PlayMode = "taiko");
|
||||||
@ -245,7 +249,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
||||||
|
|
||||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||||
AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.MatchesOnlineID(getImport().Beatmaps[expectedDiff - 1]));
|
AddUntilStep("recommended beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(getImport().Beatmaps[expectedDiff - 1].OnlineID));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override TestOsuGame CreateTestGame() => new NoBeatmapUpdateGame(LocalStorage, API);
|
protected override TestOsuGame CreateTestGame() => new NoBeatmapUpdateGame(LocalStorage, API);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
<PackageReference Include="DeepEqual" Version="4.2.1" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
<PackageReference Include="Moq" Version="4.18.4" />
|
<PackageReference Include="Moq" Version="4.18.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
<StartupObject>osu.Game.Tournament.Tests.TournamentTestRunner</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
|
@ -220,6 +220,7 @@ namespace osu.Game.Configuration
|
|||||||
|
|
||||||
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
SetDefault(OsuSetting.AlwaysShowHoldForMenuButton, false);
|
||||||
SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false);
|
SetDefault(OsuSetting.AlwaysRequireHoldingForPause, false);
|
||||||
|
SetDefault(OsuSetting.EditorShowStoryboard, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
|
||||||
@ -455,5 +456,6 @@ namespace osu.Game.Configuration
|
|||||||
MultiplayerShowInProgressFilter,
|
MultiplayerShowInProgressFilter,
|
||||||
BeatmapListingFeaturedArtistFilter,
|
BeatmapListingFeaturedArtistFilter,
|
||||||
ShowMobileDisclaimer,
|
ShowMobileDisclaimer,
|
||||||
|
EditorShowStoryboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ namespace osu.Game.Database
|
|||||||
/// <para>
|
/// <para>
|
||||||
/// If a write transaction did not modify any objects in this <see cref="IRealmCollection{T}" />, 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>.
|
/// 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="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>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// At the time when the block is called, the <see cref="IRealmCollection{T}" /> object will be fully evaluated
|
/// At the time when the block is called, the <see cref="IRealmCollection{T}" /> object will be fully evaluated
|
||||||
@ -285,8 +285,8 @@ namespace osu.Game.Database
|
|||||||
/// A subscription token. It must be kept alive for as long as you want to receive change notifications.
|
/// 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="IDisposable.Dispose" />.
|
/// To stop receiving notifications, call <see cref="IDisposable.Dispose" />.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IList{T}, NotificationCallbackDelegate{T})" />
|
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IList{T}, NotificationCallbackDelegate{T},KeyPathsCollection?)" />
|
||||||
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IQueryable{T}, NotificationCallbackDelegate{T})" />
|
/// <seealso cref="Realms.CollectionExtensions.SubscribeForNotifications{T}(IQueryable{T}, NotificationCallbackDelegate{T},KeyPathsCollection?)" />
|
||||||
#pragma warning restore RS0030
|
#pragma warning restore RS0030
|
||||||
public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback)
|
public static IDisposable QueryAsyncWithNotifications<T>(this IRealmCollection<T> collection, NotificationCallbackDelegate<T> callback)
|
||||||
where T : RealmObjectBase
|
where T : RealmObjectBase
|
||||||
|
@ -46,7 +46,8 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
public IRealmCollection<T> Freeze() => throw new NotImplementedException();
|
public IRealmCollection<T> Freeze() => throw new NotImplementedException();
|
||||||
public IDisposable SubscribeForNotifications(NotificationCallbackDelegate<T> callback) => throw new NotImplementedException();
|
public IDisposable SubscribeForNotifications(NotificationCallbackDelegate<T> callback, KeyPathsCollection? keyPathCollection = null) => throw new NotImplementedException();
|
||||||
|
|
||||||
public bool IsValid => throw new NotImplementedException();
|
public bool IsValid => throw new NotImplementedException();
|
||||||
public Realm Realm => throw new NotImplementedException();
|
public Realm Realm => throw new NotImplementedException();
|
||||||
public ObjectSchema ObjectSchema => throw new NotImplementedException();
|
public ObjectSchema ObjectSchema => throw new NotImplementedException();
|
||||||
|
@ -15,10 +15,9 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Import");
|
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Import");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way."
|
/// "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Description => new TranslatableString(getKey(@"description"),
|
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way.");
|
||||||
@"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way.");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "previous osu! install"
|
/// "previous osu! install"
|
||||||
@ -38,8 +37,7 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Your import will continue in the background. Check on its progress in the notifications sidebar!"
|
/// "Your import will continue in the background. Check on its progress in the notifications sidebar!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ImportInProgress =>
|
public static LocalisableString ImportInProgress => new TranslatableString(getKey(@"import_in_progress"), @"Your import will continue in the background. Check on its progress in the notifications sidebar!");
|
||||||
new TranslatableString(getKey(@"import_in_progress"), @"Your import will continue in the background. Check on its progress in the notifications sidebar!");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "calculating..."
|
/// "calculating..."
|
||||||
|
@ -84,12 +84,12 @@ Please try changing your audio device to a working setting.");
|
|||||||
public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!");
|
public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You received a private message from '{0}'. Click to read it!"
|
/// "You received a private message from '{0}'. Click to read it!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString PrivateMessageReceived(string username) => new TranslatableString(getKey(@"private_message_received"), @"You received a private message from '{0}'. Click to read it!", username);
|
public static LocalisableString PrivateMessageReceived(string username) => new TranslatableString(getKey(@"private_message_received"), @"You received a private message from '{0}'. Click to read it!", username);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Your name was mentioned in chat by '{0}'. Click to find out why!"
|
/// "Your name was mentioned in chat by '{0}'. Click to find out why!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username);
|
public static LocalisableString YourNameWasMentioned(string username) => new TranslatableString(getKey(@"your_name_was_mentioned"), @"Your name was mentioned in chat by '{0}'. Click to find out why!", username);
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ Please try changing your audio device to a working setting.");
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You are now running osu! {0}.
|
/// "You are now running osu! {0}.
|
||||||
/// Click to see what's new!"
|
/// Click to see what's new!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}.
|
public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}.
|
||||||
Click to see what's new!", version);
|
Click to see what's new!", version);
|
||||||
|
@ -10,10 +10,12 @@ using osu.Game.Configuration;
|
|||||||
|
|
||||||
namespace osu.Game.Online.API
|
namespace osu.Game.Online.API
|
||||||
{
|
{
|
||||||
public class ModSettingsDictionaryFormatter : IMessagePackFormatter<Dictionary<string, object>>
|
public class ModSettingsDictionaryFormatter : IMessagePackFormatter<Dictionary<string, object>?>
|
||||||
{
|
{
|
||||||
public void Serialize(ref MessagePackWriter writer, Dictionary<string, object> value, MessagePackSerializerOptions options)
|
public void Serialize(ref MessagePackWriter writer, Dictionary<string, object>? value, MessagePackSerializerOptions options)
|
||||||
{
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
|
||||||
var primitiveFormatter = PrimitiveObjectFormatter.Instance;
|
var primitiveFormatter = PrimitiveObjectFormatter.Instance;
|
||||||
|
|
||||||
writer.WriteArrayHeader(value.Count);
|
writer.WriteArrayHeader(value.Count);
|
||||||
|
@ -4,13 +4,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Online.Chat
|
namespace osu.Game.Online.Chat
|
||||||
@ -23,9 +26,15 @@ namespace osu.Game.Online.Chat
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private Clipboard clipboard { get; set; } = null!;
|
private Clipboard clipboard { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private IDialogOverlay? dialogOverlay { get; set; }
|
private IDialogOverlay? dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private INotificationOverlay? notificationOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
private Bindable<bool> externalLinkWarning = null!;
|
private Bindable<bool> externalLinkWarning = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -34,9 +43,51 @@ namespace osu.Game.Online.Chat
|
|||||||
externalLinkWarning = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning);
|
externalLinkWarning = config.GetBindable<bool>(OsuSetting.ExternalLinkWarning);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenUrlExternally(string url, bool bypassWarning = false)
|
public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default)
|
||||||
{
|
{
|
||||||
if (!bypassWarning && externalLinkWarning.Value && dialogOverlay != null)
|
bool isTrustedDomain;
|
||||||
|
|
||||||
|
if (url.StartsWith('/'))
|
||||||
|
{
|
||||||
|
url = $"{api.WebsiteRootUrl}{url}";
|
||||||
|
isTrustedDomain = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isTrustedDomain = url.StartsWith(api.WebsiteRootUrl, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!url.CheckIsValidUrl())
|
||||||
|
{
|
||||||
|
notificationOverlay?.Post(new SimpleErrorNotification
|
||||||
|
{
|
||||||
|
Text = NotificationsStrings.UnsupportedOrDangerousUrlProtocol(url),
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldWarn;
|
||||||
|
|
||||||
|
switch (warnMode)
|
||||||
|
{
|
||||||
|
case LinkWarnMode.Default:
|
||||||
|
shouldWarn = externalLinkWarning.Value && !isTrustedDomain;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkWarnMode.AlwaysWarn:
|
||||||
|
shouldWarn = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LinkWarnMode.NeverWarn:
|
||||||
|
shouldWarn = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(warnMode), warnMode, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogOverlay != null && shouldWarn)
|
||||||
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url), () => clipboard.SetText(url)));
|
dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url), () => clipboard.SetText(url)));
|
||||||
else
|
else
|
||||||
host.OpenUrlExternally(url);
|
host.OpenUrlExternally(url);
|
||||||
|
23
osu.Game/Online/Chat/LinkWarnMode.cs
Normal file
23
osu.Game/Online/Chat/LinkWarnMode.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Chat
|
||||||
|
{
|
||||||
|
public enum LinkWarnMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Will show a dialog when opening a URL that is not on a trusted domain.
|
||||||
|
/// </summary>
|
||||||
|
Default,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will always show a dialog when opening a URL.
|
||||||
|
/// </summary>
|
||||||
|
AlwaysWarn,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will never show a dialog when opening a URL.
|
||||||
|
/// </summary>
|
||||||
|
NeverWarn,
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using MessagePack;
|
|||||||
|
|
||||||
namespace osu.Game.Online.Multiplayer.MatchTypes.TeamVersus
|
namespace osu.Game.Online.Multiplayer.MatchTypes.TeamVersus
|
||||||
{
|
{
|
||||||
|
[MessagePackObject]
|
||||||
public class TeamVersusUserState : MatchUserState
|
public class TeamVersusUserState : MatchUserState
|
||||||
{
|
{
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
|
@ -18,7 +18,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -516,32 +515,7 @@ namespace osu.Game
|
|||||||
onScreenDisplay.Display(new CopyUrlToast());
|
onScreenDisplay.Display(new CopyUrlToast());
|
||||||
});
|
});
|
||||||
|
|
||||||
public void OpenUrlExternally(string url, bool forceBypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ =>
|
public void OpenUrlExternally(string url, LinkWarnMode warnMode = LinkWarnMode.Default) => waitForReady(() => externalLinkOpener, _ => externalLinkOpener.OpenUrlExternally(url, warnMode));
|
||||||
{
|
|
||||||
bool isTrustedDomain;
|
|
||||||
|
|
||||||
if (url.StartsWith('/'))
|
|
||||||
{
|
|
||||||
url = $"{API.WebsiteRootUrl}{url}";
|
|
||||||
isTrustedDomain = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
isTrustedDomain = url.StartsWith(API.WebsiteRootUrl, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url.CheckIsValidUrl())
|
|
||||||
{
|
|
||||||
Notifications.Post(new SimpleErrorNotification
|
|
||||||
{
|
|
||||||
Text = NotificationsStrings.UnsupportedOrDangerousUrlProtocol(url),
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
externalLinkOpener.OpenUrlExternally(url, forceBypassExternalUrlWarning || isTrustedDomain);
|
|
||||||
});
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a specific channel in chat.
|
/// Open a specific channel in chat.
|
||||||
@ -1340,7 +1314,7 @@ namespace osu.Game
|
|||||||
IconColour = Colours.YellowDark,
|
IconColour = Colours.YellowDark,
|
||||||
Activated = () =>
|
Activated = () =>
|
||||||
{
|
{
|
||||||
OpenUrlExternally("https://opentabletdriver.net/Tablets", true);
|
OpenUrlExternally("https://opentabletdriver.net/Tablets", LinkWarnMode.NeverWarn);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -213,7 +214,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
if (!string.IsNullOrEmpty(errors.Message))
|
if (!string.IsNullOrEmpty(errors.Message))
|
||||||
passwordDescription.AddErrors(new[] { errors.Message });
|
passwordDescription.AddErrors(new[] { errors.Message });
|
||||||
|
|
||||||
game?.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true);
|
game?.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", LinkWarnMode.NeverWarn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Header.Components
|
namespace osu.Game.Overlays.Profile.Header.Components
|
||||||
@ -87,7 +88,8 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
{
|
{
|
||||||
background.Colour = colours.Pink;
|
background.Colour = colours.Pink;
|
||||||
|
|
||||||
Action = () => game?.OpenUrlExternally(@"/home/support");
|
// Easy to accidentally click so let's always show the open URL popup.
|
||||||
|
Action = () => game?.OpenUrlExternally(@"/home/support", LinkWarnMode.AlwaysWarn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -191,9 +191,14 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<TernaryButton> CreateTernaryButtons() => new[]
|
public IEnumerable<DrawableTernaryButton> CreateTernaryButtons() => new[]
|
||||||
{
|
{
|
||||||
new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap })
|
new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = DistanceSnapToggle,
|
||||||
|
Description = "Distance Snap",
|
||||||
|
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap },
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public void HandleToggleViaKey(KeyboardEvent key)
|
public void HandleToggleViaKey(KeyboardEvent key)
|
||||||
|
@ -269,10 +269,9 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
TernaryStates = CreateTernaryButtons().ToArray();
|
togglesCollection.AddRange(CreateTernaryButtons().ToArray());
|
||||||
togglesCollection.AddRange(TernaryStates.Select(b => new DrawableTernaryButton(b)));
|
|
||||||
|
|
||||||
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates.Zip(BlueprintContainer.SampleAdditionBankTernaryStates).Select(b => new SampleBankTernaryButton(b.First, b.Second)));
|
sampleBankTogglesCollection.AddRange(BlueprintContainer.SampleBankTernaryStates);
|
||||||
|
|
||||||
SetSelectTool();
|
SetSelectTool();
|
||||||
|
|
||||||
@ -368,15 +367,10 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected abstract IReadOnlyList<CompositionTool> CompositionTools { get; }
|
protected abstract IReadOnlyList<CompositionTool> CompositionTools { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A collection of states which will be displayed to the user in the toolbox.
|
|
||||||
/// </summary>
|
|
||||||
public TernaryButton[] TernaryStates { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all ternary states required to be displayed to the user.
|
/// Create all ternary states required to be displayed to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual IEnumerable<TernaryButton> CreateTernaryButtons() => BlueprintContainer.MainTernaryStates;
|
protected virtual IEnumerable<DrawableTernaryButton> CreateTernaryButtons() => BlueprintContainer.MainTernaryStates;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
||||||
@ -437,7 +431,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button)
|
if (togglesCollection.ElementAtOrDefault(rightIndex) is DrawableTernaryButton button)
|
||||||
{
|
{
|
||||||
button.Button.Toggle();
|
button.Toggle();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Spacing = new Vector2(0, 5),
|
Spacing = new Vector2(0, 5),
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
new DrawableTernaryButton(new TernaryButton(showSpeedChanges, "Show speed changes", () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt }))
|
new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = showSpeedChanges,
|
||||||
|
Description = "Show speed changes",
|
||||||
|
CreateIcon = () => new SpriteIcon { Icon = FontAwesome.Solid.TachometerAlt },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -27,7 +27,10 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private InputManager inputManager;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public int RecordFrameRate = 60;
|
/// <summary>
|
||||||
|
/// The frame rate to record replays at.
|
||||||
|
/// </summary>
|
||||||
|
public int RecordFrameRate { get; set; } = 60;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorClient spectatorClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
@ -76,7 +79,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
var last = target.Replay.Frames.LastOrDefault();
|
var last = target.Replay.Frames.LastOrDefault();
|
||||||
|
|
||||||
if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate))
|
if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate) * Clock.Rate)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position;
|
var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position;
|
||||||
|
@ -101,18 +101,6 @@ namespace osu.Game.Screens.Backgrounds
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reloads beatmap's background.
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshBackground()
|
|
||||||
{
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
cancellationSource?.Cancel();
|
|
||||||
LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void switchBackground(BeatmapBackground b)
|
private void switchBackground(BeatmapBackground b)
|
||||||
{
|
{
|
||||||
float newDepth = 0;
|
float newDepth = 0;
|
||||||
|
117
osu.Game/Screens/Backgrounds/EditorBackgroundScreen.cs
Normal file
117
osu.Game/Screens/Backgrounds/EditorBackgroundScreen.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
using osu.Game.Storyboards.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Backgrounds
|
||||||
|
{
|
||||||
|
public partial class EditorBackgroundScreen : BackgroundScreen
|
||||||
|
{
|
||||||
|
private readonly WorkingBeatmap beatmap;
|
||||||
|
private readonly Container dimContainer;
|
||||||
|
|
||||||
|
private CancellationTokenSource? cancellationTokenSource;
|
||||||
|
private Bindable<float> dimLevel = null!;
|
||||||
|
private Bindable<bool> showStoryboard = null!;
|
||||||
|
|
||||||
|
private BeatmapBackground background = null!;
|
||||||
|
private Container storyboardContainer = null!;
|
||||||
|
|
||||||
|
private IFrameBasedClock? clockSource;
|
||||||
|
|
||||||
|
public EditorBackgroundScreen(WorkingBeatmap beatmap)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
|
||||||
|
InternalChild = dimContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
dimContainer.AddRange(createContent());
|
||||||
|
background = dimContainer.OfType<BeatmapBackground>().Single();
|
||||||
|
storyboardContainer = dimContainer.OfType<Container>().Single();
|
||||||
|
|
||||||
|
dimLevel = config.GetBindable<float>(OsuSetting.EditorDim);
|
||||||
|
showStoryboard = config.GetBindable<bool>(OsuSetting.EditorShowStoryboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Drawable> createContent() =>
|
||||||
|
[
|
||||||
|
new BeatmapBackground(beatmap) { RelativeSizeAxes = Axes.Both, },
|
||||||
|
// this kooky container nesting is here because the storyboard needs a custom clock
|
||||||
|
// but also needs it on an isolated-enough level that doesn't break screen stack expiry logic (which happens if the clock was put on `this`),
|
||||||
|
// or doesn't make it literally impossible to fade the storyboard in/out in real time (which happens if the fade transforms were to be applied directly to the storyboard).
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new DrawableStoryboard(beatmap.Storyboard)
|
||||||
|
{
|
||||||
|
Clock = clockSource ?? Clock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
dimLevel.BindValueChanged(_ => dimContainer.FadeColour(OsuColour.Gray(1 - dimLevel.Value), 500, Easing.OutQuint), true);
|
||||||
|
showStoryboard.BindValueChanged(_ => updateState());
|
||||||
|
updateState(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(double duration = 500)
|
||||||
|
{
|
||||||
|
storyboardContainer.FadeTo(showStoryboard.Value ? 1 : 0, duration, Easing.OutQuint);
|
||||||
|
// yes, this causes overdraw, but is also a (crude) fix for bad-looking transitions on screen entry
|
||||||
|
// caused by the previous background on the background stack poking out from under this one and then instantly fading out
|
||||||
|
background.FadeColour(beatmap.Storyboard.ReplacesBackground && showStoryboard.Value ? Colour4.Black : Colour4.White, duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeClockSource(IFrameBasedClock frameBasedClock)
|
||||||
|
{
|
||||||
|
clockSource = frameBasedClock;
|
||||||
|
if (IsLoaded)
|
||||||
|
storyboardContainer.Child.Clock = frameBasedClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshBackground()
|
||||||
|
{
|
||||||
|
cancellationTokenSource?.Cancel();
|
||||||
|
LoadComponentsAsync(createContent(), loaded =>
|
||||||
|
{
|
||||||
|
dimContainer.Clear();
|
||||||
|
dimContainer.AddRange(loaded);
|
||||||
|
|
||||||
|
background = dimContainer.OfType<BeatmapBackground>().Single();
|
||||||
|
storyboardContainer = dimContainer.OfType<Container>().Single();
|
||||||
|
updateState(0);
|
||||||
|
}, (cancellationTokenSource ??= new CancellationTokenSource()).Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(BackgroundScreen? other)
|
||||||
|
{
|
||||||
|
if (other is not EditorBackgroundScreen otherBeatmapBackground)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.Equals(other) && beatmap == otherBeatmapBackground.beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -18,8 +17,6 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
|
|
||||||
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
protected readonly IBindable<Track> Track = new Bindable<Track>();
|
|
||||||
|
|
||||||
public readonly Drawable Background;
|
public readonly Drawable Background;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
@ -45,10 +42,9 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, EditorClock clock)
|
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
Beatmap.BindTo(beatmap);
|
Beatmap.BindTo(beatmap);
|
||||||
Track.BindTo(clock.Track);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
@ -75,7 +76,7 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Track.BindValueChanged(tr => tr.NewValue?.AddAdjustment(AdjustableProperty.Tempo, tempoAdjustment), true);
|
editorClock.AudioAdjustments.AddAdjustment(AdjustableProperty.Tempo, tempoAdjustment);
|
||||||
|
|
||||||
if (editor != null)
|
if (editor != null)
|
||||||
currentScreenMode.BindTo(editor.Mode);
|
currentScreenMode.BindTo(editor.Mode);
|
||||||
@ -105,7 +106,8 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempoAdjustment);
|
if (editorClock.IsNotNull())
|
||||||
|
editorClock.AudioAdjustments.RemoveAdjustment(AdjustableProperty.Tempo, tempoAdjustment);
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
@ -148,7 +150,7 @@ namespace osu.Game.Screens.Edit.Components
|
|||||||
public LocalisableString TooltipText { get; set; }
|
public LocalisableString TooltipText { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class PlaybackTabControl : OsuTabControl<double>
|
public partial class PlaybackTabControl : OsuTabControl<double>
|
||||||
{
|
{
|
||||||
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
|
private static readonly double[] tempo_values = { 0.25, 0.5, 0.75, 1 };
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -16,8 +19,29 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
||||||
{
|
{
|
||||||
public partial class DrawableTernaryButton : OsuButton, IHasTooltip
|
public partial class DrawableTernaryButton : OsuButton, IHasTooltip, IHasCurrentValue<TernaryState>
|
||||||
{
|
{
|
||||||
|
public Bindable<TernaryState> Current
|
||||||
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BindableWithCurrent<TernaryState> current = new BindableWithCurrent<TernaryState>();
|
||||||
|
|
||||||
|
public required LocalisableString Description
|
||||||
|
{
|
||||||
|
get => Text;
|
||||||
|
set => Text = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalisableString TooltipText { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Drawable>? CreateIcon { get; init; }
|
||||||
|
|
||||||
private Color4 defaultBackgroundColour;
|
private Color4 defaultBackgroundColour;
|
||||||
private Color4 defaultIconColour;
|
private Color4 defaultIconColour;
|
||||||
private Color4 selectedBackgroundColour;
|
private Color4 selectedBackgroundColour;
|
||||||
@ -25,14 +49,8 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
|
|
||||||
protected Drawable Icon { get; private set; } = null!;
|
protected Drawable Icon { get; private set; } = null!;
|
||||||
|
|
||||||
public readonly TernaryButton Button;
|
public DrawableTernaryButton()
|
||||||
|
|
||||||
public DrawableTernaryButton(TernaryButton button)
|
|
||||||
{
|
{
|
||||||
Button = button;
|
|
||||||
|
|
||||||
Text = button.Description;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +63,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
defaultIconColour = defaultBackgroundColour.Darken(0.5f);
|
defaultIconColour = defaultBackgroundColour.Darken(0.5f);
|
||||||
selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
|
selectedIconColour = selectedBackgroundColour.Lighten(0.5f);
|
||||||
|
|
||||||
Add(Icon = (Button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
|
Add(Icon = (CreateIcon?.Invoke() ?? new Circle()).With(b =>
|
||||||
{
|
{
|
||||||
b.Blending = BlendingParameters.Additive;
|
b.Blending = BlendingParameters.Additive;
|
||||||
b.Anchor = Anchor.CentreLeft;
|
b.Anchor = Anchor.CentreLeft;
|
||||||
@ -59,18 +77,32 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
Button.Bindable.BindValueChanged(_ => updateSelectionState(), true);
|
current.BindValueChanged(_ => updateSelectionState(), true);
|
||||||
Button.Enabled.BindTo(Enabled);
|
|
||||||
|
|
||||||
Action = onAction;
|
Action = onAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAction()
|
private void onAction()
|
||||||
{
|
{
|
||||||
if (!Button.Enabled.Value)
|
if (!Enabled.Value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Button.Toggle();
|
Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Toggle()
|
||||||
|
{
|
||||||
|
switch (Current.Value)
|
||||||
|
{
|
||||||
|
case TernaryState.False:
|
||||||
|
case TernaryState.Indeterminate:
|
||||||
|
Current.Value = TernaryState.True;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.True:
|
||||||
|
Current.Value = TernaryState.False;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectionState()
|
private void updateSelectionState()
|
||||||
@ -78,7 +110,7 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
if (!IsLoaded)
|
if (!IsLoaded)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (Button.Bindable.Value)
|
switch (Current.Value)
|
||||||
{
|
{
|
||||||
case TernaryState.Indeterminate:
|
case TernaryState.Indeterminate:
|
||||||
Icon.Colour = selectedIconColour.Darken(0.5f);
|
Icon.Colour = selectedIconColour.Darken(0.5f);
|
||||||
@ -104,7 +136,5 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
X = 40f
|
X = 40f
|
||||||
};
|
};
|
||||||
|
|
||||||
public LocalisableString TooltipText => Button.Tooltip;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,32 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Humanizer;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
||||||
{
|
{
|
||||||
public partial class SampleBankTernaryButton : CompositeDrawable
|
public partial class SampleBankTernaryButton : CompositeDrawable
|
||||||
{
|
{
|
||||||
public readonly TernaryButton NormalButton;
|
public string BankName { get; }
|
||||||
public readonly TernaryButton AdditionsButton;
|
public Func<Drawable>? CreateIcon { get; init; }
|
||||||
|
|
||||||
public SampleBankTernaryButton(TernaryButton normalButton, TernaryButton additionsButton)
|
public readonly BindableWithCurrent<TernaryState> NormalState = new BindableWithCurrent<TernaryState>();
|
||||||
|
public readonly BindableWithCurrent<TernaryState> AdditionsState = new BindableWithCurrent<TernaryState>();
|
||||||
|
|
||||||
|
public DrawableTernaryButton NormalButton { get; private set; } = null!;
|
||||||
|
public DrawableTernaryButton AdditionsButton { get; private set; } = null!;
|
||||||
|
|
||||||
|
public SampleBankTernaryButton(string bankName)
|
||||||
{
|
{
|
||||||
NormalButton = normalButton;
|
BankName = bankName;
|
||||||
AdditionsButton = additionsButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -36,7 +45,12 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Padding = new MarginPadding { Right = 1 },
|
Padding = new MarginPadding { Right = 1 },
|
||||||
Child = new InlineDrawableTernaryButton(NormalButton),
|
Child = NormalButton = new InlineDrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = NormalState,
|
||||||
|
Description = BankName.Titleize(),
|
||||||
|
CreateIcon = CreateIcon,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -46,18 +60,18 @@ namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Padding = new MarginPadding { Left = 1 },
|
Padding = new MarginPadding { Left = 1 },
|
||||||
Child = new InlineDrawableTernaryButton(AdditionsButton),
|
Child = AdditionsButton = new InlineDrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = AdditionsState,
|
||||||
|
Description = BankName.Titleize(),
|
||||||
|
CreateIcon = CreateIcon,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class InlineDrawableTernaryButton : DrawableTernaryButton
|
private partial class InlineDrawableTernaryButton : DrawableTernaryButton
|
||||||
{
|
{
|
||||||
public InlineDrawableTernaryButton(TernaryButton button)
|
|
||||||
: base(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -1,48 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.TernaryButtons
|
|
||||||
{
|
|
||||||
public class TernaryButton
|
|
||||||
{
|
|
||||||
public readonly Bindable<TernaryState> Bindable;
|
|
||||||
|
|
||||||
public readonly Bindable<bool> Enabled = new Bindable<bool>(true);
|
|
||||||
|
|
||||||
public readonly string Description;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
|
||||||
/// </summary>
|
|
||||||
public readonly Func<Drawable>? CreateIcon;
|
|
||||||
|
|
||||||
public string Tooltip { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public TernaryButton(Bindable<TernaryState> bindable, string description, Func<Drawable>? createIcon = null)
|
|
||||||
{
|
|
||||||
Bindable = bindable;
|
|
||||||
Description = description;
|
|
||||||
CreateIcon = createIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Toggle()
|
|
||||||
{
|
|
||||||
switch (Bindable.Value)
|
|
||||||
{
|
|
||||||
case TernaryState.False:
|
|
||||||
case TernaryState.Indeterminate:
|
|
||||||
Bindable.Value = TernaryState.True;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TernaryState.True:
|
|
||||||
Bindable.Value = TernaryState.False;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -26,7 +26,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
||||||
|
|
||||||
protected readonly IBindable<Track> Track = new Bindable<Track>();
|
[Resolved]
|
||||||
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
private readonly Container<T> content;
|
private readonly Container<T> content;
|
||||||
|
|
||||||
@ -35,22 +36,17 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
public TimelinePart(Container<T>? content = null)
|
public TimelinePart(Container<T>? content = null)
|
||||||
{
|
{
|
||||||
AddInternal(this.content = content ?? new Container<T> { RelativeSizeAxes = Axes.Both });
|
AddInternal(this.content = content ?? new Container<T> { RelativeSizeAxes = Axes.Both });
|
||||||
|
|
||||||
beatmap.ValueChanged += _ =>
|
|
||||||
{
|
|
||||||
updateRelativeChildSize();
|
|
||||||
};
|
|
||||||
|
|
||||||
Track.ValueChanged += _ => updateRelativeChildSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, EditorClock clock)
|
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
this.beatmap.BindTo(beatmap);
|
this.beatmap.BindTo(beatmap);
|
||||||
LoadBeatmap(EditorBeatmap);
|
LoadBeatmap(EditorBeatmap);
|
||||||
|
|
||||||
Track.BindTo(clock.Track);
|
this.beatmap.ValueChanged += _ => updateRelativeChildSize();
|
||||||
|
editorClock.TrackChanged += updateRelativeChildSize;
|
||||||
|
updateRelativeChildSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRelativeChildSize()
|
private void updateRelativeChildSize()
|
||||||
@ -68,5 +64,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
|
|||||||
{
|
{
|
||||||
content.Clear();
|
content.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editorClock.IsNotNull())
|
||||||
|
editorClock.TrackChanged -= updateRelativeChildSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,11 +65,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
MainTernaryStates = CreateTernaryButtons().ToArray();
|
MainTernaryStates = CreateTernaryButtons().ToArray();
|
||||||
SampleBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionBankStates).ToArray();
|
SampleBankTernaryStates = createSampleBankTernaryButtons().ToArray();
|
||||||
SampleAdditionBankTernaryStates = createSampleBankTernaryButtons(SelectionHandler.SelectionAdditionBankStates).ToArray();
|
|
||||||
|
|
||||||
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
|
|
||||||
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
|
|
||||||
|
|
||||||
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
|
AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset)
|
||||||
{
|
{
|
||||||
@ -98,6 +94,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
|
foreach (var kvp in SelectionHandler.SelectionAdditionBankStates)
|
||||||
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
|
kvp.Value.BindValueChanged(_ => updatePlacementSamples());
|
||||||
|
|
||||||
|
SelectionHandler.AutoSelectionBankEnabled.BindValueChanged(_ => updateAutoBankTernaryButtonTooltip(), true);
|
||||||
|
SelectionHandler.SelectionAdditionBanksEnabled.BindValueChanged(_ => updateAdditionBankTernaryButtonTooltips(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
protected override void TransferBlueprintFor(HitObject hitObject, DrawableHitObject drawableObject)
|
||||||
@ -238,28 +237,45 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A collection of states which will be displayed to the user in the toolbox.
|
/// A collection of states which will be displayed to the user in the toolbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TernaryButton[] MainTernaryStates { get; private set; }
|
public DrawableTernaryButton[] MainTernaryStates { get; private set; }
|
||||||
|
|
||||||
public TernaryButton[] SampleBankTernaryStates { get; private set; }
|
public SampleBankTernaryButton[] SampleBankTernaryStates { get; private set; }
|
||||||
|
|
||||||
public TernaryButton[] SampleAdditionBankTernaryStates { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all ternary states required to be displayed to the user.
|
/// Create all ternary states required to be displayed to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual IEnumerable<TernaryButton> CreateTernaryButtons()
|
protected virtual IEnumerable<DrawableTernaryButton> CreateTernaryButtons()
|
||||||
{
|
{
|
||||||
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
|
//TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects.
|
||||||
yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA });
|
yield return new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = NewCombo,
|
||||||
|
Description = "New combo",
|
||||||
|
CreateIcon = () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA },
|
||||||
|
};
|
||||||
|
|
||||||
foreach (var kvp in SelectionHandler.SelectionSampleStates)
|
foreach (var kvp in SelectionHandler.SelectionSampleStates)
|
||||||
yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => GetIconForSample(kvp.Key));
|
{
|
||||||
|
yield return new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = kvp.Value,
|
||||||
|
Description = kvp.Key.Replace(@"hit", string.Empty).Titleize(),
|
||||||
|
CreateIcon = () => GetIconForSample(kvp.Key),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TernaryButton> createSampleBankTernaryButtons(Dictionary<string, Bindable<TernaryState>> sampleBankStates)
|
private IEnumerable<SampleBankTernaryButton> createSampleBankTernaryButtons()
|
||||||
{
|
{
|
||||||
foreach (var kvp in sampleBankStates)
|
foreach (string bankName in HitSampleInfo.ALL_BANKS.Prepend(EditorSelectionHandler.HIT_BANK_AUTO))
|
||||||
yield return new TernaryButton(kvp.Value, kvp.Key.Titleize(), () => getIconForBank(kvp.Key));
|
{
|
||||||
|
yield return new SampleBankTernaryButton(bankName)
|
||||||
|
{
|
||||||
|
NormalState = { Current = SelectionHandler.SelectionBankStates[bankName], },
|
||||||
|
AdditionsState = { Current = SelectionHandler.SelectionAdditionBankStates[bankName], },
|
||||||
|
CreateIcon = () => getIconForBank(bankName)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getIconForBank(string sampleName)
|
private Drawable getIconForBank(string sampleName)
|
||||||
@ -295,19 +311,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
{
|
{
|
||||||
bool enabled = SelectionHandler.AutoSelectionBankEnabled.Value;
|
bool enabled = SelectionHandler.AutoSelectionBankEnabled.Value;
|
||||||
|
|
||||||
var autoBankButton = SampleBankTernaryStates.Single(t => t.Bindable == SelectionHandler.SelectionBankStates[EditorSelectionHandler.HIT_BANK_AUTO]);
|
var autoBankButton = SampleBankTernaryStates.Single(t => t.BankName == EditorSelectionHandler.HIT_BANK_AUTO);
|
||||||
autoBankButton.Enabled.Value = enabled;
|
autoBankButton.NormalButton.Enabled.Value = enabled;
|
||||||
autoBankButton.Tooltip = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
|
autoBankButton.NormalButton.TooltipText = !enabled ? "Auto normal bank can only be used during hit object placement" : string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAdditionBankTernaryButtonTooltips()
|
private void updateAdditionBankTernaryButtonTooltips()
|
||||||
{
|
{
|
||||||
bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value;
|
bool enabled = SelectionHandler.SelectionAdditionBanksEnabled.Value;
|
||||||
|
|
||||||
foreach (var ternaryButton in SampleAdditionBankTernaryStates)
|
foreach (var ternaryButton in SampleBankTernaryStates)
|
||||||
{
|
{
|
||||||
ternaryButton.Enabled.Value = enabled;
|
ternaryButton.AdditionsButton.Enabled.Value = enabled;
|
||||||
ternaryButton.Tooltip = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
|
ternaryButton.AdditionsButton.TooltipText = !enabled ? "Add an addition sample first to be able to set a bank" : string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +300,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
createStateBindables();
|
createStateBindables();
|
||||||
updateTernaryStates();
|
updateTernaryStates();
|
||||||
togglesCollection.AddRange(createTernaryButtons().Select(b => new DrawableTernaryButton(b) { RelativeSizeAxes = Axes.None, Size = new Vector2(40, 40) }));
|
togglesCollection.AddRange(createTernaryButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1
|
private string? getCommonBank() => allRelevantSamples.Select(h => GetBankValue(h.samples)).Distinct().Count() == 1
|
||||||
@ -444,10 +444,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TernaryButton> createTernaryButtons()
|
private IEnumerable<DrawableTernaryButton> createTernaryButtons()
|
||||||
{
|
{
|
||||||
foreach ((string sampleName, var bindable) in selectionSampleStates)
|
foreach ((string sampleName, var bindable) in selectionSampleStates)
|
||||||
yield return new TernaryButton(bindable, string.Empty, () => ComposeBlueprintContainer.GetIconForSample(sampleName));
|
{
|
||||||
|
yield return new DrawableTernaryButton
|
||||||
|
{
|
||||||
|
Current = bindable,
|
||||||
|
Description = string.Empty,
|
||||||
|
CreateIcon = () => ComposeBlueprintContainer.GetIconForSample(sampleName),
|
||||||
|
RelativeSizeAxes = Axes.None,
|
||||||
|
Size = new Vector2(40, 40),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addHitSample(string sampleName)
|
private void addHitSample(string sampleName)
|
||||||
@ -516,7 +525,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
|
if (item is not DrawableTernaryButton button) return base.OnKeyDown(e);
|
||||||
|
|
||||||
button.Button.Toggle();
|
button.Toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -49,6 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timeline's scroll position in the last frame.
|
/// The timeline's scroll position in the last frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -86,8 +89,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private double trackLengthForZoom;
|
private double trackLengthForZoom;
|
||||||
|
|
||||||
private readonly IBindable<Track> track = new Bindable<Track>();
|
|
||||||
|
|
||||||
public Timeline(Drawable userContent)
|
public Timeline(Drawable userContent)
|
||||||
{
|
{
|
||||||
this.userContent = userContent;
|
this.userContent = userContent;
|
||||||
@ -101,7 +102,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OverlayColourProvider colourProvider, OsuConfigManager config)
|
private void load(OsuColour colours, OverlayColourProvider colourProvider, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
CentreMarker centreMarker;
|
CentreMarker centreMarker;
|
||||||
|
|
||||||
@ -150,16 +151,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
controlPointsVisible = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
controlPointsVisible = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTimingChanges);
|
||||||
ticksVisible = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
ticksVisible = config.GetBindable<bool>(OsuSetting.EditorTimelineShowTicks);
|
||||||
|
|
||||||
track.BindTo(editorClock.Track);
|
editorClock.TrackChanged += updateWaveform;
|
||||||
track.BindValueChanged(_ =>
|
updateWaveform();
|
||||||
{
|
|
||||||
waveform.Waveform = beatmap.Value.Waveform;
|
|
||||||
Scheduler.AddOnce(applyVisualOffset, beatmap);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
Zoom = (float)(defaultTimelineZoom * editorBeatmap.TimelineZoom);
|
Zoom = (float)(defaultTimelineZoom * editorBeatmap.TimelineZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateWaveform()
|
||||||
|
{
|
||||||
|
waveform.Waveform = beatmap.Value.Waveform;
|
||||||
|
Scheduler.AddOnce(applyVisualOffset, beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
private void applyVisualOffset(IBindable<WorkingBeatmap> beatmap)
|
private void applyVisualOffset(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
waveform.RelativePositionAxes = Axes.X;
|
waveform.RelativePositionAxes = Axes.X;
|
||||||
@ -334,5 +337,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X);
|
double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X);
|
||||||
return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time));
|
return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editorClock.IsNotNull())
|
||||||
|
editorClock.TrackChanged -= updateWaveform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ using osu.Game.Rulesets;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Screens.Edit.Components.Menus;
|
using osu.Game.Screens.Edit.Components.Menus;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
@ -54,7 +55,6 @@ using osu.Game.Screens.Edit.Setup;
|
|||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osu.Game.Screens.Edit.Verify;
|
using osu.Game.Screens.Edit.Verify;
|
||||||
using osu.Game.Screens.OnlinePlay;
|
using osu.Game.Screens.OnlinePlay;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
using WebCommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||||
@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
[Cached(typeof(IBeatSnapProvider))]
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
[Cached]
|
[Cached]
|
||||||
public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
|
public partial class Editor : OsuScreen, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An offset applied to waveform visuals to align them with expectations.
|
/// An offset applied to waveform visuals to align them with expectations.
|
||||||
@ -210,6 +210,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||||
|
|
||||||
private Bindable<float> editorBackgroundDim;
|
private Bindable<float> editorBackgroundDim;
|
||||||
|
private Bindable<bool> editorShowStoryboard;
|
||||||
private Bindable<bool> editorHitMarkers;
|
private Bindable<bool> editorHitMarkers;
|
||||||
private Bindable<bool> editorAutoSeekOnPlacement;
|
private Bindable<bool> editorAutoSeekOnPlacement;
|
||||||
private Bindable<bool> editorLimitedDistanceSnap;
|
private Bindable<bool> editorLimitedDistanceSnap;
|
||||||
@ -320,6 +321,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
OsuMenuItem redoMenuItem;
|
OsuMenuItem redoMenuItem;
|
||||||
|
|
||||||
editorBackgroundDim = config.GetBindable<float>(OsuSetting.EditorDim);
|
editorBackgroundDim = config.GetBindable<float>(OsuSetting.EditorDim);
|
||||||
|
editorShowStoryboard = config.GetBindable<bool>(OsuSetting.EditorShowStoryboard);
|
||||||
editorHitMarkers = config.GetBindable<bool>(OsuSetting.EditorShowHitMarkers);
|
editorHitMarkers = config.GetBindable<bool>(OsuSetting.EditorShowHitMarkers);
|
||||||
editorAutoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
editorAutoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
||||||
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
editorLimitedDistanceSnap = config.GetBindable<bool>(OsuSetting.EditorLimitedDistanceSnap);
|
||||||
@ -398,7 +400,13 @@ namespace osu.Game.Screens.Edit
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
new OsuMenuItemSpacer(),
|
||||||
new BackgroundDimMenuItem(editorBackgroundDim),
|
new BackgroundDimMenuItem(editorBackgroundDim),
|
||||||
|
new ToggleMenuItem("Show storyboard")
|
||||||
|
{
|
||||||
|
State = { BindTarget = editorShowStoryboard },
|
||||||
|
},
|
||||||
|
new OsuMenuItemSpacer(),
|
||||||
new ToggleMenuItem(EditorStrings.ShowHitMarkers)
|
new ToggleMenuItem(EditorStrings.ShowHitMarkers)
|
||||||
{
|
{
|
||||||
State = { BindTarget = editorHitMarkers },
|
State = { BindTarget = editorHitMarkers },
|
||||||
@ -466,12 +474,14 @@ namespace osu.Game.Screens.Edit
|
|||||||
changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||||
changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
|
changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||||
|
|
||||||
editorBackgroundDim.BindValueChanged(_ => dimBackground());
|
editorBackgroundDim.BindValueChanged(_ => setUpBackground());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private MusicController musicController { get; set; }
|
private MusicController musicController { get; set; }
|
||||||
|
|
||||||
|
protected override BackgroundScreen CreateBackground() => new EditorBackgroundScreen(Beatmap.Value);
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
@ -853,23 +863,23 @@ namespace osu.Game.Screens.Edit
|
|||||||
public override void OnEntering(ScreenTransitionEvent e)
|
public override void OnEntering(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
base.OnEntering(e);
|
base.OnEntering(e);
|
||||||
dimBackground();
|
setUpBackground();
|
||||||
resetTrack(true);
|
resetTrack(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnResuming(ScreenTransitionEvent e)
|
public override void OnResuming(ScreenTransitionEvent e)
|
||||||
{
|
{
|
||||||
base.OnResuming(e);
|
base.OnResuming(e);
|
||||||
dimBackground();
|
setUpBackground();
|
||||||
|
clock.BindAdjustments();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void dimBackground()
|
private void setUpBackground()
|
||||||
{
|
{
|
||||||
ApplyToBackground(b =>
|
ApplyToBackground(b =>
|
||||||
{
|
{
|
||||||
b.IgnoreUserSettings.Value = true;
|
var editorBackground = (EditorBackgroundScreen)b;
|
||||||
b.DimWhenUserSettingsIgnored.Value = editorBackgroundDim.Value;
|
editorBackground.ChangeClockSource(clock);
|
||||||
b.BlurAmount.Value = 0;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -908,11 +918,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
beatmap.EditorTimestamp = clock.CurrentTime;
|
beatmap.EditorTimestamp = clock.CurrentTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
ApplyToBackground(b =>
|
|
||||||
{
|
|
||||||
b.DimWhenUserSettingsIgnored.Value = 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
resetTrack();
|
resetTrack();
|
||||||
|
|
||||||
refetchBeatmap();
|
refetchBeatmap();
|
||||||
@ -925,6 +930,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
base.OnSuspending(e);
|
base.OnSuspending(e);
|
||||||
clock.Stop();
|
clock.Stop();
|
||||||
refetchBeatmap();
|
refetchBeatmap();
|
||||||
|
// unfortunately ordering matters here.
|
||||||
|
// this unbind MUST happen after `refetchBeatmap()`, because along other things, `refetchBeatmap()` causes a global working beatmap change,
|
||||||
|
// which causes `EditorClock` to reload the track and automatically reapply adjustments to it.
|
||||||
|
clock.UnbindAdjustments();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refetchBeatmap()
|
private void refetchBeatmap()
|
||||||
|
@ -41,6 +41,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
rulesetBeatmapProcessor?.PostProcess();
|
rulesetBeatmapProcessor?.PostProcess();
|
||||||
|
|
||||||
autoGenerateBreaks();
|
autoGenerateBreaks();
|
||||||
|
ensureNewComboAfterBreaks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void autoGenerateBreaks()
|
private void autoGenerateBreaks()
|
||||||
@ -100,5 +101,40 @@ namespace osu.Game.Screens.Edit
|
|||||||
Beatmap.Breaks.Add(breakPeriod);
|
Beatmap.Breaks.Add(breakPeriod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ensureNewComboAfterBreaks()
|
||||||
|
{
|
||||||
|
var breakEnds = Beatmap.Breaks.Select(b => b.EndTime).OrderBy(t => t).ToList();
|
||||||
|
|
||||||
|
if (breakEnds.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int currentBreak = 0;
|
||||||
|
|
||||||
|
IHasComboInformation? lastObj = null;
|
||||||
|
bool comboInformationUpdateRequired = false;
|
||||||
|
|
||||||
|
foreach (var hitObject in Beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
if (hitObject is not IHasComboInformation hasCombo)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (currentBreak < breakEnds.Count && hitObject.StartTime >= breakEnds[currentBreak])
|
||||||
|
{
|
||||||
|
if (!hasCombo.NewCombo)
|
||||||
|
{
|
||||||
|
hasCombo.NewCombo = true;
|
||||||
|
comboInformationUpdateRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBreak += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comboInformationUpdateRequired)
|
||||||
|
hasCombo.UpdateComboInformation(lastObj);
|
||||||
|
|
||||||
|
lastObj = hasCombo;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -23,12 +25,15 @@ namespace osu.Game.Screens.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class EditorClock : CompositeComponent, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock
|
public partial class EditorClock : CompositeComponent, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock
|
||||||
{
|
{
|
||||||
public IBindable<Track> Track => track;
|
[CanBeNull]
|
||||||
|
public event Action TrackChanged;
|
||||||
|
|
||||||
private readonly Bindable<Track> track = new Bindable<Track>();
|
private readonly Bindable<Track> track = new Bindable<Track>();
|
||||||
|
|
||||||
public double TrackLength => track.Value?.IsLoaded == true ? track.Value.Length : 60000;
|
public double TrackLength => track.Value?.IsLoaded == true ? track.Value.Length : 60000;
|
||||||
|
|
||||||
|
public AudioAdjustments AudioAdjustments { get; } = new AudioAdjustments();
|
||||||
|
|
||||||
public ControlPointInfo ControlPointInfo => Beatmap.ControlPointInfo;
|
public ControlPointInfo ControlPointInfo => Beatmap.ControlPointInfo;
|
||||||
|
|
||||||
public IBeatmap Beatmap { get; set; }
|
public IBeatmap Beatmap { get; set; }
|
||||||
@ -56,6 +61,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
underlyingClock = new FramedBeatmapClock(applyOffsets: true, requireDecoupling: true);
|
underlyingClock = new FramedBeatmapClock(applyOffsets: true, requireDecoupling: true);
|
||||||
AddInternal(underlyingClock);
|
AddInternal(underlyingClock);
|
||||||
|
|
||||||
|
track.BindValueChanged(_ => TrackChanged?.Invoke());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -208,7 +215,16 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetSpeedAdjustments() => underlyingClock.ResetSpeedAdjustments();
|
public void BindAdjustments() => track.Value?.BindAdjustments(AudioAdjustments);
|
||||||
|
|
||||||
|
public void UnbindAdjustments() => track.Value?.UnbindAdjustments(AudioAdjustments);
|
||||||
|
|
||||||
|
public void ResetSpeedAdjustments()
|
||||||
|
{
|
||||||
|
AudioAdjustments.RemoveAllAdjustments(AdjustableProperty.Frequency);
|
||||||
|
AudioAdjustments.RemoveAllAdjustments(AdjustableProperty.Tempo);
|
||||||
|
underlyingClock.ResetSpeedAdjustments();
|
||||||
|
}
|
||||||
|
|
||||||
double IAdjustableClock.Rate
|
double IAdjustableClock.Rate
|
||||||
{
|
{
|
||||||
@ -231,8 +247,12 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public void ChangeSource(IClock source)
|
public void ChangeSource(IClock source)
|
||||||
{
|
{
|
||||||
|
UnbindAdjustments();
|
||||||
|
|
||||||
track.Value = source as Track;
|
track.Value = source as Track;
|
||||||
underlyingClock.ChangeSource(source);
|
underlyingClock.ChangeSource(source);
|
||||||
|
|
||||||
|
BindAdjustments();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IClock Source => underlyingClock.Source;
|
public IClock Source => underlyingClock.Source;
|
||||||
|
@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Models;
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Screens.Backgrounds;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Setup
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
@ -87,7 +88,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
(metadata, name) => metadata.BackgroundFile = name);
|
(metadata, name) => metadata.BackgroundFile = name);
|
||||||
|
|
||||||
headerBackground.UpdateBackground();
|
headerBackground.UpdateBackground();
|
||||||
editor?.ApplyToBackground(bg => bg.RefreshBackground());
|
editor?.ApplyToBackground(bg => ((EditorBackgroundScreen)bg).RefreshBackground());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Cached]
|
[Cached]
|
||||||
public readonly Bindable<ControlPointGroup> SelectedGroup = new Bindable<ControlPointGroup>();
|
public readonly Bindable<ControlPointGroup> SelectedGroup = new Bindable<ControlPointGroup>();
|
||||||
|
|
||||||
|
private readonly Bindable<EditorScreenMode> currentEditorMode = new Bindable<EditorScreenMode>();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock? editorClock { get; set; }
|
private EditorClock? editorClock { get; set; }
|
||||||
|
|
||||||
@ -41,18 +43,35 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(Editor? editor)
|
||||||
|
{
|
||||||
|
if (editor != null)
|
||||||
|
currentEditorMode.BindTo(editor.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (editorClock != null)
|
// When entering the timing screen, let's choose the closest valid timing point.
|
||||||
|
// This will emulate the osu-stable behaviour where a metronome and timing information
|
||||||
|
// are presented on entering the screen.
|
||||||
|
currentEditorMode.BindValueChanged(mode =>
|
||||||
{
|
{
|
||||||
// When entering the timing screen, let's choose the closest valid timing point.
|
if (mode.NewValue == EditorScreenMode.Timing)
|
||||||
// This will emulate the osu-stable behaviour where a metronome and timing information
|
selectClosestTimingPoint();
|
||||||
// are presented on entering the screen.
|
});
|
||||||
var nearestTimingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime);
|
selectClosestTimingPoint();
|
||||||
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
}
|
||||||
}
|
|
||||||
|
private void selectClosestTimingPoint()
|
||||||
|
{
|
||||||
|
if (editorClock == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var nearestTimingPoint = EditorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime);
|
||||||
|
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ConfigureTimeline(TimelineArea timelineArea)
|
protected override void ConfigureTimeline(TimelineArea timelineArea)
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -305,7 +305,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IBindable<Track> track = new Bindable<Track>();
|
[Resolved]
|
||||||
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
public WaveformRow(bool isMainRow)
|
public WaveformRow(bool isMainRow)
|
||||||
{
|
{
|
||||||
@ -313,7 +314,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(EditorClock clock)
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -343,13 +344,16 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Colour = colourProvider.Content2
|
Colour = colourProvider.Content2
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
track.BindTo(clock.Track);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
track.ValueChanged += _ => waveformGraph.Waveform = beatmap.Value.Waveform;
|
editorClock.TrackChanged += updateWaveform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateWaveform()
|
||||||
|
{
|
||||||
|
waveformGraph.Waveform = beatmap.Value.Waveform;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int BeatIndex { set => beatIndexText.Text = value.ToString(); }
|
public int BeatIndex { set => beatIndexText.Text = value.ToString(); }
|
||||||
@ -363,6 +367,14 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
get => waveformGraph.X;
|
get => waveformGraph.X;
|
||||||
set => waveformGraph.X = value;
|
set => waveformGraph.X = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (editorClock.IsNotNull())
|
||||||
|
editorClock.TrackChanged -= updateWaveform;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ using osu.Game.Beatmaps.Drawables;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Online.Rooms;
|
using osu.Game.Online.Rooms;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using Container = osu.Framework.Graphics.Containers.Container;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.OnlinePlay.Components
|
namespace osu.Game.Screens.OnlinePlay.Components
|
||||||
{
|
{
|
||||||
@ -30,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
private StarRatingDisplay maxDisplay = null!;
|
private StarRatingDisplay maxDisplay = null!;
|
||||||
private Drawable maxBackground = null!;
|
private Drawable maxBackground = null!;
|
||||||
|
|
||||||
|
private BufferedContainer bufferedContent = null!;
|
||||||
|
|
||||||
public StarRatingRangeDisplay(Room room)
|
public StarRatingRangeDisplay(Room room)
|
||||||
{
|
{
|
||||||
this.room = room;
|
this.room = room;
|
||||||
@ -41,38 +42,43 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Container
|
new CircularContainer
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 1,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
minBackground = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = new Vector2(0.5f),
|
|
||||||
},
|
|
||||||
maxBackground = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = new Vector2(0.5f),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Masking = true,
|
||||||
|
// Stops artifacting from boxes drawn behind wrong colour boxes (and edge pixels adding up to higher opacity).
|
||||||
|
Padding = new MarginPadding(-0.1f),
|
||||||
|
Child = bufferedContent = new BufferedContainer(pixelSnapping: true, cachedFrameBuffer: true)
|
||||||
{
|
{
|
||||||
minDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range),
|
AutoSizeAxes = Axes.Both,
|
||||||
maxDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range)
|
Children = new[]
|
||||||
|
{
|
||||||
|
minBackground = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(1, 0.5f),
|
||||||
|
},
|
||||||
|
maxBackground = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = new Vector2(1, 0.5f),
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
minDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range),
|
||||||
|
maxDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +127,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
|||||||
|
|
||||||
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
|
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
|
||||||
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
|
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
|
||||||
|
|
||||||
|
bufferedContent.ForceRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -54,6 +54,10 @@ namespace osu.Game.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
|
[Union(12, typeof(InSoloGame))]
|
||||||
|
[Union(23, typeof(InMultiplayerGame))]
|
||||||
|
[Union(24, typeof(SpectatingMultiplayerGame))]
|
||||||
|
[Union(31, typeof(InPlaylistGame))]
|
||||||
public abstract class InGame : UserActivity
|
public abstract class InGame : UserActivity
|
||||||
{
|
{
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
|
@ -20,24 +20,24 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
<PackageReference Include="DiffPlex" Version="1.7.2" />
|
<PackageReference Include="DiffPlex" Version="1.7.2" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.70" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
|
||||||
<PackageReference Include="Humanizer" Version="2.14.1" />
|
<PackageReference Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageReference Include="MessagePack" Version="2.5.187" />
|
<PackageReference Include="MessagePack" Version="3.1.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.10" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.802.0">
|
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.802.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="20.1.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1224.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.1224.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1224.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1224.0" />
|
||||||
<PackageReference Include="Sentry" Version="4.13.0" />
|
<PackageReference Include="Sentry" Version="5.0.0" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
<PackageReference Include="SharpCompress" Version="0.38.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
|
@ -840,6 +840,7 @@ See the LICENCE file in the repository root for full licence text.
|
|||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Microsoft_002EToolkit_002EHighPerformance_002EBox_002A/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Microsoft_002EToolkit_002EHighPerformance_002EBox_002A/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=OpenTabletDriver_002EPlugin_002EDependencyInjection_002E_002A/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=OpenTabletDriver_002EPlugin_002EDependencyInjection_002E_002A/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Realms_002ELogging_002ELogger/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Realms_002ELogging_002ELogger/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=Realms_002EThreadSafe_002A/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002EComponentModel_002EComponent/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002EComponentModel_002EComponent/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002EComponentModel_002EContainer/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002EComponentModel_002EContainer/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ENumerics_002E_002A/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/AutoImport2/=CSHARP/BlackLists/=System_002ENumerics_002E_002A/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
Loading…
Reference in New Issue
Block a user