mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-20 23:07:43 +01:00
Merge pull request #1549 from goatcorp/net8-rollup
This commit is contained in:
commit
95c301113e
62 changed files with 3085 additions and 774 deletions
|
|
@ -57,6 +57,7 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
|
||||||
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
|
||||||
|
dotnet_separate_import_directive_groups = true
|
||||||
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:suggestion
|
||||||
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
|
||||||
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
|
||||||
|
|
@ -97,6 +98,7 @@ resharper_apply_on_completion = true
|
||||||
resharper_auto_property_can_be_made_get_only_global_highlighting = none
|
resharper_auto_property_can_be_made_get_only_global_highlighting = none
|
||||||
resharper_auto_property_can_be_made_get_only_local_highlighting = none
|
resharper_auto_property_can_be_made_get_only_local_highlighting = none
|
||||||
resharper_autodetect_indent_settings = true
|
resharper_autodetect_indent_settings = true
|
||||||
|
resharper_blank_lines_around_single_line_auto_property = 1
|
||||||
resharper_braces_for_ifelse = required_for_multiline
|
resharper_braces_for_ifelse = required_for_multiline
|
||||||
resharper_can_use_global_alias = false
|
resharper_can_use_global_alias = false
|
||||||
resharper_csharp_align_multiline_parameter = true
|
resharper_csharp_align_multiline_parameter = true
|
||||||
|
|
@ -105,14 +107,22 @@ resharper_csharp_empty_block_style = multiline
|
||||||
resharper_csharp_int_align_comments = true
|
resharper_csharp_int_align_comments = true
|
||||||
resharper_csharp_new_line_before_while = true
|
resharper_csharp_new_line_before_while = true
|
||||||
resharper_csharp_wrap_after_declaration_lpar = true
|
resharper_csharp_wrap_after_declaration_lpar = true
|
||||||
|
resharper_csharp_wrap_after_invocation_lpar = true
|
||||||
|
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||||
resharper_enforce_line_ending_style = true
|
resharper_enforce_line_ending_style = true
|
||||||
|
resharper_instance_members_qualify_declared_in = this_class, base_class
|
||||||
resharper_member_can_be_private_global_highlighting = none
|
resharper_member_can_be_private_global_highlighting = none
|
||||||
resharper_member_can_be_private_local_highlighting = none
|
resharper_member_can_be_private_local_highlighting = none
|
||||||
resharper_new_line_before_finally = false
|
resharper_new_line_before_finally = true
|
||||||
|
resharper_parentheses_non_obvious_operations = none, multiplicative, additive, arithmetic, shift, bitwise_and, bitwise_exclusive_or, bitwise_inclusive_or, bitwise
|
||||||
|
resharper_parentheses_redundancy_style = remove_if_not_clarifies_precedence
|
||||||
resharper_place_accessorholder_attribute_on_same_line = false
|
resharper_place_accessorholder_attribute_on_same_line = false
|
||||||
resharper_place_field_attribute_on_same_line = false
|
resharper_place_field_attribute_on_same_line = false
|
||||||
|
resharper_place_simple_initializer_on_single_line = true
|
||||||
resharper_show_autodetect_configure_formatting_tip = false
|
resharper_show_autodetect_configure_formatting_tip = false
|
||||||
|
resharper_space_within_single_line_array_initializer_braces = true
|
||||||
resharper_use_indent_from_vs = false
|
resharper_use_indent_from_vs = false
|
||||||
|
resharper_wrap_array_initializer_style = chop_if_long
|
||||||
|
|
||||||
# ReSharper inspection severities
|
# ReSharper inspection severities
|
||||||
resharper_arrange_missing_parentheses_highlighting = hint
|
resharper_arrange_missing_parentheses_highlighting = hint
|
||||||
|
|
|
||||||
|
|
@ -50,4 +50,10 @@
|
||||||
<Private>false</Private>
|
<Private>false</Private>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Dalamud.CorePlugin.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
9
Dalamud.CorePlugin/Dalamud.CorePlugin.json
Normal file
9
Dalamud.CorePlugin/Dalamud.CorePlugin.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"Author": "Dalamud Maintainers",
|
||||||
|
"Name": "CorePlugin",
|
||||||
|
"Punchline": "Testbed for developing Dalamud features.",
|
||||||
|
"Description": "Develop and debug internal Dalamud features using CorePlugin. You have full access to all types in Dalamud assembly.",
|
||||||
|
"InternalName": "CorePlugin",
|
||||||
|
"ApplicableVersion": "any",
|
||||||
|
"Tags": []
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -20,7 +21,7 @@ namespace Dalamud.Configuration.Internal;
|
||||||
/// Class containing Dalamud settings.
|
/// Class containing Dalamud settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading
|
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
|
|
@ -34,7 +35,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
};
|
};
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private string configPath;
|
private string? configPath;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
private bool isSaveQueued;
|
private bool isSaveQueued;
|
||||||
|
|
@ -48,12 +49,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event that occurs when dalamud configuration is saved.
|
/// Event that occurs when dalamud configuration is saved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved;
|
public event DalamudConfigurationSavedDelegate? DalamudConfigurationSaved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a list of muted works.
|
/// Gets or sets a list of muted works.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> BadWords { get; set; }
|
public List<string>? BadWords { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
/// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found.
|
||||||
|
|
@ -68,12 +69,12 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the language code to load Dalamud localization with.
|
/// Gets or sets the language code to load Dalamud localization with.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LanguageOverride { get; set; } = null;
|
public string? LanguageOverride { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastVersion { get; set; } = null;
|
public string? LastVersion { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating the last seen FTUE version.
|
/// Gets or sets a value indicating the last seen FTUE version.
|
||||||
|
|
@ -84,7 +85,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the last loaded Dalamud version.
|
/// Gets or sets the last loaded Dalamud version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string LastChangelogMajorMinor { get; set; } = null;
|
public string? LastChangelogMajorMinor { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the chat type used by default for plugin messages.
|
/// Gets or sets the chat type used by default for plugin messages.
|
||||||
|
|
@ -229,6 +230,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
|
/// Gets or sets a value indicating whether or not plugin user interfaces should trigger sound effects.
|
||||||
/// This setting is effected by the in-game "System Sounds" option and volume.
|
/// This setting is effected by the in-game "System Sounds" option and volume.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "ABI")]
|
||||||
public bool EnablePluginUISoundEffects { get; set; }
|
public bool EnablePluginUISoundEffects { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -266,7 +268,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
/// Gets or sets the kind of beta to download when <see cref="DalamudBetaKey"/> matches the server value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DalamudBetaKind { get; set; }
|
public string? DalamudBetaKind { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
/// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started.
|
||||||
|
|
@ -514,6 +516,8 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
|
||||||
private void Save()
|
private void Save()
|
||||||
{
|
{
|
||||||
ThreadSafety.AssertMainThread();
|
ThreadSafety.AssertMainThread();
|
||||||
|
if (this.configPath is null)
|
||||||
|
throw new InvalidOperationException("configPath is not set.");
|
||||||
|
|
||||||
Service<ReliableFileStorage>.Get().WriteAllText(
|
Service<ReliableFileStorage>.Get().WriteAllText(
|
||||||
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
this.configPath, JsonConvert.SerializeObject(this, SerializerSettings));
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ namespace Dalamud;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main Dalamud class containing all subsystems.
|
/// The main Dalamud class containing all subsystems.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
internal sealed class Dalamud : IServiceType
|
internal sealed class Dalamud : IServiceType
|
||||||
{
|
{
|
||||||
#region Internals
|
#region Internals
|
||||||
|
|
|
||||||
146
Dalamud/DalamudAsset.cs
Normal file
146
Dalamud/DalamudAsset.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
namespace Dalamud;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies an asset that has been shipped as Dalamud Asset.<br />
|
||||||
|
/// <strong>Any asset can cease to exist at any point, even if the enum value exists.</strong><br />
|
||||||
|
/// Either ship your own assets, or be prepared for errors.
|
||||||
|
/// </summary>
|
||||||
|
public enum DalamudAsset
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Nothing.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Empty, data: new byte[0])]
|
||||||
|
Unspecified = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromRaw"/>: The fallback empty texture.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromRaw, data: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 })]
|
||||||
|
[DalamudAssetRawTexture(4, 8, 4, SharpDX.DXGI.Format.BC1_UNorm)]
|
||||||
|
Empty4X4 = 1000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "logo.png")]
|
||||||
|
Logo = 1001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo, but smaller.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "tsmLogo.png")]
|
||||||
|
LogoSmall = 1002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The default plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "defaultIcon.png")]
|
||||||
|
DefaultIcon = 1003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The disabled plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "disabledIcon.png")]
|
||||||
|
DisabledIcon = 1004,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The outdated installable plugin icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "outdatedInstallableIcon.png")]
|
||||||
|
OutdatedInstallableIcon = 1005,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||||
|
TroubleIcon = 1006,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin update icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "updateIcon.png")]
|
||||||
|
UpdateIcon = 1007,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin installed icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "installedIcon.png")]
|
||||||
|
InstalledIcon = 1008,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The third party plugin icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "thirdIcon.png")]
|
||||||
|
ThirdIcon = 1009,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The installed third party plugin icon overlay.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "thirdInstalledIcon.png")]
|
||||||
|
ThirdInstalledIcon = 1010,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The API bump explainer icon.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "changelogApiBump.png")]
|
||||||
|
ChangelogApiBumpIcon = 1011,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The background shade for
|
||||||
|
/// <see cref="Interface.Internal.Windows.TitleScreenMenuWindow"/>.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||||
|
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||||
|
TitleScreenMenuShade = 1012,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Regular.otf")]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Medium.otf")]
|
||||||
|
NotoSansJpMedium = 2000,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK KR Regular.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansCJKkr-Regular.otf")]
|
||||||
|
[DalamudAssetPath("UIRes", "NotoSansKR-Regular.otf")]
|
||||||
|
NotoSansKrRegular = 2001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Inconsolata Regular.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "Inconsolata-Regular.ttf")]
|
||||||
|
InconsolataRegular = 2002,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||||
|
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
||||||
|
FontAwesomeFreeSolid = 2003,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="DalamudAssetPurpose.Font"/>: Game symbol fonts being used as webfonts at Lodestone.
|
||||||
|
/// </summary>
|
||||||
|
[DalamudAsset(DalamudAssetPurpose.Font, required: false)]
|
||||||
|
// [DalamudAssetOnlineSource("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")]
|
||||||
|
LodestoneGameSymbol = 2004,
|
||||||
|
}
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.Addon.Events;
|
||||||
/// Service provider for addon event management.
|
/// Service provider for addon event management.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal unsafe class AddonEventManager : IDisposable, IServiceType
|
internal unsafe class AddonEventManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Dalamud.Game.Addon.Lifecycle;
|
||||||
/// This class provides events for in-game addon lifecycles.
|
/// This class provides events for in-game addon lifecycles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("AddonLifecycle");
|
private static readonly ModuleLog Log = new("AddonLifecycle");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Dalamud.Game.Config;
|
||||||
/// This class represents the game's configuration.
|
/// This class represents the game's configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
|
||||||
{
|
{
|
||||||
private readonly GameConfigAddressResolver address = new();
|
private readonly GameConfigAddressResolver address = new();
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace Dalamud.Game.DutyState;
|
||||||
/// This class represents the state of the currently occupied duty.
|
/// This class represents the state of the currently occupied duty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
|
||||||
{
|
{
|
||||||
private readonly DutyStateAddressResolver address;
|
private readonly DutyStateAddressResolver address;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
@ -22,14 +21,14 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
|
||||||
|
|
||||||
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
||||||
|
|
||||||
private readonly HttpClient httpClient = Service<HappyHttpClient>.Get().SharedHttpClient;
|
private readonly HttpClient httpClient;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
|
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UniversalisMarketBoardUploader()
|
/// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
|
||||||
{
|
public UniversalisMarketBoardUploader(HappyHttpClient happyHttpClient) =>
|
||||||
}
|
this.httpClient = happyHttpClient.SharedHttpClient;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task Upload(MarketBoardItemRequest request)
|
public async Task Upload(MarketBoardItemRequest request)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Dalamud.Game.Network.Internal.MarketBoardUploaders;
|
||||||
using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
|
using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
|
||||||
using Dalamud.Game.Network.Structures;
|
using Dalamud.Game.Network.Structures;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Networking.Http;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -23,7 +24,7 @@ namespace Dalamud.Game.Network.Internal;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class handles network notifications and uploading market board data.
|
/// This class handles network notifications and uploading market board data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal unsafe class NetworkHandlers : IDisposable, IServiceType
|
internal unsafe class NetworkHandlers : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
private readonly IMarketBoardUploader uploader;
|
private readonly IMarketBoardUploader uploader;
|
||||||
|
|
@ -55,9 +56,12 @@ internal unsafe class NetworkHandlers : IDisposable, IServiceType
|
||||||
private bool disposing;
|
private bool disposing;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private NetworkHandlers(GameNetwork gameNetwork, TargetSigScanner sigScanner)
|
private NetworkHandlers(
|
||||||
|
GameNetwork gameNetwork,
|
||||||
|
TargetSigScanner sigScanner,
|
||||||
|
HappyHttpClient happyHttpClient)
|
||||||
{
|
{
|
||||||
this.uploader = new UniversalisMarketBoardUploader();
|
this.uploader = new UniversalisMarketBoardUploader(happyHttpClient);
|
||||||
|
|
||||||
this.addressResolver = new NetworkHandlersAddressResolver();
|
this.addressResolver = new NetworkHandlersAddressResolver();
|
||||||
this.addressResolver.Setup(sigScanner);
|
this.addressResolver.Setup(sigScanner);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ namespace Dalamud.Game;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<ISigScanner>]
|
[ResolveVia<ISigScanner>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceT
|
||||||
|
|
||||||
foreach (var hook in notDisposed)
|
foreach (var hook in notDisposed)
|
||||||
{
|
{
|
||||||
|
Log.Warning("\t\t\tLeaked hook at +0x{Address:X}", hook.Address.ToInt64() - this.scanner.Module.BaseAddress.ToInt64());
|
||||||
hook.Dispose();
|
hook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Interface.DragDrop;
|
||||||
/// and can be used to create ImGui drag and drop sources and targets for those external events.
|
/// and can be used to create ImGui drag and drop sources and targets for those external events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
[ResolveVia<IDragDropManager>]
|
[ResolveVia<IDragDropManager>]
|
||||||
#pragma warning restore SA1015
|
#pragma warning restore SA1015
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
|
@ -22,7 +21,7 @@ public class FdtReader
|
||||||
for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++)
|
for (var i = 0; i < this.FontHeader.FontTableEntryCount; i++)
|
||||||
this.Glyphs.Add(StructureFromByteArray<FontTableEntry>(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf<FontTableHeader>() + (Marshal.SizeOf<FontTableEntry>() * i)));
|
this.Glyphs.Add(StructureFromByteArray<FontTableEntry>(data, this.FileHeader.FontTableHeaderOffset + Marshal.SizeOf<FontTableHeader>() + (Marshal.SizeOf<FontTableEntry>() * i)));
|
||||||
|
|
||||||
for (int i = 0, i_ = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < i_; i++)
|
for (int i = 0, to = Math.Min(this.FontHeader.KerningTableEntryCount, this.KerningHeader.Count); i < to; i++)
|
||||||
this.Distances.Add(StructureFromByteArray<KerningTableEntry>(data, this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf<KerningTableHeader>() + (Marshal.SizeOf<KerningTableEntry>() * i)));
|
this.Distances.Add(StructureFromByteArray<KerningTableEntry>(data, this.FileHeader.KerningTableHeaderOffset + Marshal.SizeOf<KerningTableHeader>() + (Marshal.SizeOf<KerningTableEntry>() * i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +50,14 @@ public class FdtReader
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<KerningTableEntry> Distances { get; init; } = new();
|
public List<KerningTableEntry> Distances { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the glyph index for the corresponding codepoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="codepoint">Unicode codepoint (UTF-32 value).</param>
|
||||||
|
/// <returns>Corresponding index, or a negative number according to <see cref="List{T}.BinarySearch(int,int,T,System.Collections.Generic.IComparer{T}?)"/>.</returns>
|
||||||
|
public int FindGlyphIndex(int codepoint) =>
|
||||||
|
this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) });
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds glyph definition for corresponding codepoint.
|
/// Finds glyph definition for corresponding codepoint.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -58,7 +65,7 @@ public class FdtReader
|
||||||
/// <returns>Corresponding FontTableEntry, or null if not found.</returns>
|
/// <returns>Corresponding FontTableEntry, or null if not found.</returns>
|
||||||
public FontTableEntry? FindGlyph(int codepoint)
|
public FontTableEntry? FindGlyph(int codepoint)
|
||||||
{
|
{
|
||||||
var i = this.Glyphs.BinarySearch(new FontTableEntry { CharUtf8 = CodePointToUtf8Int32(codepoint) });
|
var i = this.FindGlyphIndex(codepoint);
|
||||||
if (i < 0 || i == this.Glyphs.Count)
|
if (i < 0 || i == this.Glyphs.Count)
|
||||||
return null;
|
return null;
|
||||||
return this.Glyphs[i];
|
return this.Glyphs[i];
|
||||||
|
|
@ -91,17 +98,12 @@ public class FdtReader
|
||||||
return this.Distances[i].RightOffset;
|
return this.Distances[i].RightOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe T StructureFromByteArray<T>(byte[] data, int offset)
|
/// <summary>
|
||||||
{
|
/// Translates a UTF-32 codepoint to a <see cref="uint"/> containing a UTF-8 character.
|
||||||
var len = Marshal.SizeOf<T>();
|
/// </summary>
|
||||||
if (offset + len > data.Length)
|
/// <param name="codepoint">The codepoint.</param>
|
||||||
throw new Exception("Data too short");
|
/// <returns>The uint.</returns>
|
||||||
|
internal static int CodePointToUtf8Int32(int codepoint)
|
||||||
fixed (byte* ptr = data)
|
|
||||||
return Marshal.PtrToStructure<T>(new(ptr + offset));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int CodePointToUtf8Int32(int codepoint)
|
|
||||||
{
|
{
|
||||||
if (codepoint <= 0x7F)
|
if (codepoint <= 0x7F)
|
||||||
{
|
{
|
||||||
|
|
@ -131,6 +133,16 @@ public class FdtReader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static unsafe T StructureFromByteArray<T>(byte[] data, int offset)
|
||||||
|
{
|
||||||
|
var len = Marshal.SizeOf<T>();
|
||||||
|
if (offset + len > data.Length)
|
||||||
|
throw new Exception("Data too short");
|
||||||
|
|
||||||
|
fixed (byte* ptr = data)
|
||||||
|
return Marshal.PtrToStructure<T>(new(ptr + offset));
|
||||||
|
}
|
||||||
|
|
||||||
private static int Utf8Uint32ToCodePoint(int n)
|
private static int Utf8Uint32ToCodePoint(int n)
|
||||||
{
|
{
|
||||||
if ((n & 0xFFFFFF80) == 0)
|
if ((n & 0xFFFFFF80) == 0)
|
||||||
|
|
@ -252,7 +264,7 @@ public class FdtReader
|
||||||
/// Glyph table entry.
|
/// Glyph table entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe struct FontTableEntry : IComparable<FontTableEntry>
|
public struct FontTableEntry : IComparable<FontTableEntry>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mapping of texture channel index to byte index.
|
/// Mapping of texture channel index to byte index.
|
||||||
|
|
@ -367,7 +379,7 @@ public class FdtReader
|
||||||
/// Kerning table entry.
|
/// Kerning table entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe struct KerningTableEntry : IComparable<KerningTableEntry>
|
public struct KerningTableEntry : IComparable<KerningTableEntry>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the left character.
|
/// Integer representation of a Unicode character in UTF-8 in reverse order, read in little endian, for the left character.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Dalamud.Interface.GameFonts;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads game font for use in ImGui.
|
/// Loads game font for use in ImGui.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal class GameFontManager : IServiceType
|
internal class GameFontManager : IServiceType
|
||||||
{
|
{
|
||||||
private static readonly string?[] FontNames =
|
private static readonly string?[] FontNames =
|
||||||
|
|
@ -257,7 +257,7 @@ internal class GameFontManager : IServiceType
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
public void CopyGlyphsAcrossFonts(ImFontPtr? source, GameFontStyle target, bool missingOnly, bool rebuildLookupTable)
|
||||||
{
|
{
|
||||||
ImGuiHelpers.CopyGlyphsAcrossFonts(source, this.fonts[target], missingOnly, rebuildLookupTable);
|
ImGuiHelpers.CopyGlyphsAcrossFonts(source ?? default, this.fonts[target], missingOnly, rebuildLookupTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -269,7 +269,7 @@ internal class GameFontManager : IServiceType
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
|
public void CopyGlyphsAcrossFonts(GameFontStyle source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable)
|
||||||
{
|
{
|
||||||
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target, missingOnly, rebuildLookupTable);
|
ImGuiHelpers.CopyGlyphsAcrossFonts(this.fonts[source], target ?? default, missingOnly, rebuildLookupTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.GameFonts;
|
namespace Dalamud.Interface.GameFonts;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -153,7 +151,7 @@ public struct GameFontStyle
|
||||||
GameFontFamilyAndSize.TrumpGothic184 => 18.4f,
|
GameFontFamilyAndSize.TrumpGothic184 => 18.4f,
|
||||||
GameFontFamilyAndSize.TrumpGothic23 => 23,
|
GameFontFamilyAndSize.TrumpGothic23 => 23,
|
||||||
GameFontFamilyAndSize.TrumpGothic34 => 34,
|
GameFontFamilyAndSize.TrumpGothic34 => 34,
|
||||||
GameFontFamilyAndSize.TrumpGothic68 => 8,
|
GameFontFamilyAndSize.TrumpGothic68 => 68,
|
||||||
_ => throw new InvalidOperationException(),
|
_ => throw new InvalidOperationException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -186,77 +184,77 @@ public struct GameFontStyle
|
||||||
/// <param name="family">Font family.</param>
|
/// <param name="family">Font family.</param>
|
||||||
/// <param name="size">Font size in points.</param>
|
/// <param name="size">Font size in points.</param>
|
||||||
/// <returns>Recommended GameFontFamilyAndSize.</returns>
|
/// <returns>Recommended GameFontFamilyAndSize.</returns>
|
||||||
public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size)
|
public static GameFontFamilyAndSize GetRecommendedFamilyAndSize(GameFontFamily family, float size) =>
|
||||||
{
|
family switch
|
||||||
if (size <= 0)
|
|
||||||
return GameFontFamilyAndSize.Undefined;
|
|
||||||
|
|
||||||
switch (family)
|
|
||||||
{
|
{
|
||||||
case GameFontFamily.Undefined:
|
_ when size <= 0 => GameFontFamilyAndSize.Undefined,
|
||||||
return GameFontFamilyAndSize.Undefined;
|
GameFontFamily.Undefined => GameFontFamilyAndSize.Undefined,
|
||||||
|
GameFontFamily.Axis => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((9.6f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis96,
|
||||||
|
<= ((int)((12f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis12,
|
||||||
|
<= ((int)((14f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis14,
|
||||||
|
<= ((int)((18f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Axis18,
|
||||||
|
_ => GameFontFamilyAndSize.Axis36,
|
||||||
|
},
|
||||||
|
GameFontFamily.Jupiter => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((16f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter16,
|
||||||
|
<= ((int)((20f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter20,
|
||||||
|
<= ((int)((23f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter23,
|
||||||
|
_ => GameFontFamilyAndSize.Jupiter46,
|
||||||
|
},
|
||||||
|
GameFontFamily.JupiterNumeric => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((45f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Jupiter45,
|
||||||
|
_ => GameFontFamilyAndSize.Jupiter90,
|
||||||
|
},
|
||||||
|
GameFontFamily.Meidinger => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((16f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Meidinger16,
|
||||||
|
<= ((int)((20f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.Meidinger20,
|
||||||
|
_ => GameFontFamilyAndSize.Meidinger40,
|
||||||
|
},
|
||||||
|
GameFontFamily.MiedingerMid => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((10f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid10,
|
||||||
|
<= ((int)((12f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid12,
|
||||||
|
<= ((int)((14f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid14,
|
||||||
|
<= ((int)((18f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.MiedingerMid18,
|
||||||
|
_ => GameFontFamilyAndSize.MiedingerMid36,
|
||||||
|
},
|
||||||
|
GameFontFamily.TrumpGothic => size switch
|
||||||
|
{
|
||||||
|
<= ((int)((18.4f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic184,
|
||||||
|
<= ((int)((23f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic23,
|
||||||
|
<= ((int)((34f * 4f / 3f) + 0.5f) * 3f / 4f) + 0.001f => GameFontFamilyAndSize.TrumpGothic34,
|
||||||
|
_ => GameFontFamilyAndSize.TrumpGothic68,
|
||||||
|
},
|
||||||
|
_ => GameFontFamilyAndSize.Undefined,
|
||||||
|
};
|
||||||
|
|
||||||
case GameFontFamily.Axis:
|
/// <summary>
|
||||||
if (size <= 9.601)
|
/// Calculates the adjustment to width resulting fron Weight and SkewStrength.
|
||||||
return GameFontFamilyAndSize.Axis96;
|
/// </summary>
|
||||||
else if (size <= 12.001)
|
/// <param name="header">Font header.</param>
|
||||||
return GameFontFamilyAndSize.Axis12;
|
/// <param name="glyph">Glyph.</param>
|
||||||
else if (size <= 14.001)
|
/// <returns>Width adjustment in pixel unit.</returns>
|
||||||
return GameFontFamilyAndSize.Axis14;
|
public int CalculateBaseWidthAdjustment(in FdtReader.FontTableHeader header, in FdtReader.FontTableEntry glyph)
|
||||||
else if (size <= 18.001)
|
{
|
||||||
return GameFontFamilyAndSize.Axis18;
|
var widthDelta = this.Weight;
|
||||||
else
|
switch (this.BaseSkewStrength)
|
||||||
return GameFontFamilyAndSize.Axis36;
|
{
|
||||||
|
case > 0:
|
||||||
case GameFontFamily.Jupiter:
|
widthDelta += (1f * this.BaseSkewStrength * (header.LineHeight - glyph.CurrentOffsetY))
|
||||||
if (size <= 16.001)
|
/ header.LineHeight;
|
||||||
return GameFontFamilyAndSize.Jupiter16;
|
break;
|
||||||
else if (size <= 20.001)
|
case < 0:
|
||||||
return GameFontFamilyAndSize.Jupiter20;
|
widthDelta -= (1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight))
|
||||||
else if (size <= 23.001)
|
/ header.LineHeight;
|
||||||
return GameFontFamilyAndSize.Jupiter23;
|
break;
|
||||||
else
|
|
||||||
return GameFontFamilyAndSize.Jupiter46;
|
|
||||||
|
|
||||||
case GameFontFamily.JupiterNumeric:
|
|
||||||
if (size <= 45.001)
|
|
||||||
return GameFontFamilyAndSize.Jupiter45;
|
|
||||||
else
|
|
||||||
return GameFontFamilyAndSize.Jupiter90;
|
|
||||||
|
|
||||||
case GameFontFamily.Meidinger:
|
|
||||||
if (size <= 16.001)
|
|
||||||
return GameFontFamilyAndSize.Meidinger16;
|
|
||||||
else if (size <= 20.001)
|
|
||||||
return GameFontFamilyAndSize.Meidinger20;
|
|
||||||
else
|
|
||||||
return GameFontFamilyAndSize.Meidinger40;
|
|
||||||
|
|
||||||
case GameFontFamily.MiedingerMid:
|
|
||||||
if (size <= 10.001)
|
|
||||||
return GameFontFamilyAndSize.MiedingerMid10;
|
|
||||||
else if (size <= 12.001)
|
|
||||||
return GameFontFamilyAndSize.MiedingerMid12;
|
|
||||||
else if (size <= 14.001)
|
|
||||||
return GameFontFamilyAndSize.MiedingerMid14;
|
|
||||||
else if (size <= 18.001)
|
|
||||||
return GameFontFamilyAndSize.MiedingerMid18;
|
|
||||||
else
|
|
||||||
return GameFontFamilyAndSize.MiedingerMid36;
|
|
||||||
|
|
||||||
case GameFontFamily.TrumpGothic:
|
|
||||||
if (size <= 18.401)
|
|
||||||
return GameFontFamilyAndSize.TrumpGothic184;
|
|
||||||
else if (size <= 23.001)
|
|
||||||
return GameFontFamilyAndSize.TrumpGothic23;
|
|
||||||
else if (size <= 34.001)
|
|
||||||
return GameFontFamilyAndSize.TrumpGothic34;
|
|
||||||
else
|
|
||||||
return GameFontFamilyAndSize.TrumpGothic68;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return GameFontFamilyAndSize.Undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (int)MathF.Ceiling(widthDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -265,16 +263,8 @@ public struct GameFontStyle
|
||||||
/// <param name="reader">Font information.</param>
|
/// <param name="reader">Font information.</param>
|
||||||
/// <param name="glyph">Glyph.</param>
|
/// <param name="glyph">Glyph.</param>
|
||||||
/// <returns>Width adjustment in pixel unit.</returns>
|
/// <returns>Width adjustment in pixel unit.</returns>
|
||||||
public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph)
|
public int CalculateBaseWidthAdjustment(FdtReader reader, FdtReader.FontTableEntry glyph) =>
|
||||||
{
|
this.CalculateBaseWidthAdjustment(reader.FontHeader, glyph);
|
||||||
var widthDelta = this.Weight;
|
|
||||||
if (this.BaseSkewStrength > 0)
|
|
||||||
widthDelta += 1f * this.BaseSkewStrength * (reader.FontHeader.LineHeight - glyph.CurrentOffsetY) / reader.FontHeader.LineHeight;
|
|
||||||
else if (this.BaseSkewStrength < 0)
|
|
||||||
widthDelta -= 1f * this.BaseSkewStrength * (glyph.CurrentOffsetY + glyph.BoundingHeight) / reader.FontHeader.LineHeight;
|
|
||||||
|
|
||||||
return (int)Math.Ceiling(widthDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using Dalamud.IoC.Internal;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class containing various textures used by Dalamud windows for branding purposes.
|
|
||||||
/// </summary>
|
|
||||||
[ServiceManager.EarlyLoadedService]
|
|
||||||
#pragma warning disable SA1015
|
|
||||||
[InherentDependency<InterfaceManager.InterfaceManagerWithScene>] // Can't load textures before this
|
|
||||||
#pragma warning restore SA1015
|
|
||||||
internal class Branding : IServiceType, IDisposable
|
|
||||||
{
|
|
||||||
private readonly Dalamud dalamud;
|
|
||||||
private readonly TextureManager tm;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Branding"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dalamud">Dalamud instance.</param>
|
|
||||||
/// <param name="tm">TextureManager instance.</param>
|
|
||||||
[ServiceManager.ServiceConstructor]
|
|
||||||
public Branding(Dalamud dalamud, TextureManager tm)
|
|
||||||
{
|
|
||||||
this.dalamud = dalamud;
|
|
||||||
this.tm = tm;
|
|
||||||
|
|
||||||
this.LoadTextures();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a full-size Dalamud logo texture.
|
|
||||||
/// </summary>
|
|
||||||
public IDalamudTextureWrap Logo { get; private set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a small Dalamud logo texture.
|
|
||||||
/// </summary>
|
|
||||||
public IDalamudTextureWrap LogoSmall { get; private set; } = null!;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
this.Logo.Dispose();
|
|
||||||
this.LogoSmall.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadTextures()
|
|
||||||
{
|
|
||||||
this.Logo = this.tm.GetTextureFromFile(new FileInfo(Path.Combine(this.dalamud.AssetDirectory.FullName, "UIRes", "logo.png")))
|
|
||||||
?? throw new Exception("Could not load logo.");
|
|
||||||
|
|
||||||
this.LogoSmall = this.tm.GetTextureFromFile(new FileInfo(Path.Combine(this.dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png")))
|
|
||||||
?? throw new Exception("Could not load TSM logo.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
@ -9,7 +7,9 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Internal;
|
using Dalamud.Game.Internal;
|
||||||
using Dalamud.Interface.Animation.EasingFunctions;
|
using Dalamud.Interface.Animation.EasingFunctions;
|
||||||
|
|
@ -25,14 +25,15 @@ using Dalamud.Interface.Style;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging;
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
|
||||||
using ImPlotNET;
|
using ImPlotNET;
|
||||||
using PInvoke;
|
using PInvoke;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
@ -48,9 +49,11 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
private const float CreditsDarkeningMaxAlpha = 0.8f;
|
private const float CreditsDarkeningMaxAlpha = 0.8f;
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("DUI");
|
private static readonly ModuleLog Log = new("DUI");
|
||||||
|
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
private readonly DalamudConfiguration configuration;
|
private readonly DalamudConfiguration configuration;
|
||||||
|
private readonly InterfaceManager interfaceManager;
|
||||||
|
|
||||||
private readonly ChangelogWindow changelogWindow;
|
private readonly ChangelogWindow changelogWindow;
|
||||||
private readonly ColorDemoWindow colorDemoWindow;
|
private readonly ColorDemoWindow colorDemoWindow;
|
||||||
private readonly ComponentDemoWindow componentDemoWindow;
|
private readonly ComponentDemoWindow componentDemoWindow;
|
||||||
|
|
@ -92,11 +95,16 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
DalamudConfiguration configuration,
|
DalamudConfiguration configuration,
|
||||||
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
|
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
|
||||||
PluginImageCache pluginImageCache,
|
PluginImageCache pluginImageCache,
|
||||||
Branding branding)
|
DalamudAssetManager dalamudAssetManager,
|
||||||
|
Game.Framework framework,
|
||||||
|
ClientState clientState,
|
||||||
|
TitleScreenMenu titleScreenMenu,
|
||||||
|
GameGui gameGui)
|
||||||
{
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
|
this.interfaceManager = interfaceManagerWithScene.Manager;
|
||||||
|
|
||||||
var interfaceManager = interfaceManagerWithScene.Manager;
|
|
||||||
this.WindowSystem = new WindowSystem("DalamudCore");
|
this.WindowSystem = new WindowSystem("DalamudCore");
|
||||||
|
|
||||||
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
|
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
|
||||||
|
|
@ -104,13 +112,19 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
this.dataWindow = new DataWindow() { IsOpen = false };
|
this.dataWindow = new DataWindow() { IsOpen = false };
|
||||||
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
|
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
|
||||||
this.imeWindow = new ImeWindow() { IsOpen = false };
|
this.imeWindow = new ImeWindow() { IsOpen = false };
|
||||||
this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup };
|
this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
|
||||||
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
|
||||||
this.pluginWindow = new PluginInstallerWindow(pluginImageCache) { IsOpen = false };
|
this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
|
||||||
this.settingsWindow = new SettingsWindow() { IsOpen = false };
|
this.settingsWindow = new SettingsWindow() { IsOpen = false };
|
||||||
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
|
this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
|
||||||
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
||||||
this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false };
|
this.titleScreenMenuWindow = new TitleScreenMenuWindow(
|
||||||
|
clientState,
|
||||||
|
configuration,
|
||||||
|
dalamudAssetManager,
|
||||||
|
framework,
|
||||||
|
gameGui,
|
||||||
|
titleScreenMenu) { IsOpen = false };
|
||||||
this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
|
this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
|
||||||
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
||||||
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
|
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
|
||||||
|
|
@ -136,16 +150,34 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
|
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
|
||||||
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
|
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
|
||||||
|
|
||||||
interfaceManager.Draw += this.OnDraw;
|
this.interfaceManager.Draw += this.OnDraw;
|
||||||
|
|
||||||
var tsm = Service<TitleScreenMenu>.Get();
|
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(
|
||||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), branding.LogoSmall, () => this.OpenPluginInstaller());
|
_ =>
|
||||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), branding.LogoSmall, this.OpenSettings);
|
{
|
||||||
|
titleScreenMenu.AddEntryCore(
|
||||||
|
Loc.Localize("TSMDalamudPlugins", "Plugin Installer"),
|
||||||
|
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||||
|
this.OpenPluginInstaller);
|
||||||
|
titleScreenMenu.AddEntryCore(
|
||||||
|
Loc.Localize("TSMDalamudSettings", "Dalamud Settings"),
|
||||||
|
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||||
|
this.OpenSettings);
|
||||||
|
|
||||||
if (!configuration.DalamudBetaKind.IsNullOrEmpty())
|
titleScreenMenu.AddEntryCore(
|
||||||
{
|
"Toggle Dev Menu",
|
||||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), branding.LogoSmall, () => this.isImGuiDrawDevMenu = true);
|
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||||
}
|
() => Service<DalamudInterface>.GetNullable()?.ToggleDevMenu(),
|
||||||
|
VirtualKey.SHIFT);
|
||||||
|
|
||||||
|
if (!configuration.DalamudBetaKind.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
titleScreenMenu.AddEntryCore(
|
||||||
|
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
|
||||||
|
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||||
|
() => this.isImGuiDrawDevMenu = true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
|
this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
|
||||||
this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha);
|
this.creditsDarkeningAnimation.Point2 = new Vector2(CreditsDarkeningMaxAlpha);
|
||||||
|
|
@ -173,7 +205,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Service<InterfaceManager>.Get().Draw -= this.OnDraw;
|
this.interfaceManager.Draw -= this.OnDraw;
|
||||||
|
|
||||||
this.WindowSystem.RemoveAllWindows();
|
this.WindowSystem.RemoveAllWindows();
|
||||||
|
|
||||||
|
|
@ -356,7 +388,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
/// Toggles the <see cref="DataWindow"/>.
|
/// Toggles the <see cref="DataWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataKind">The data kind to switch to after opening.</param>
|
/// <param name="dataKind">The data kind to switch to after opening.</param>
|
||||||
public void ToggleDataWindow(string dataKind = null)
|
public void ToggleDataWindow(string? dataKind = null)
|
||||||
{
|
{
|
||||||
this.dataWindow.Toggle();
|
this.dataWindow.Toggle();
|
||||||
if (dataKind != null && this.dataWindow.IsOpen)
|
if (dataKind != null && this.dataWindow.IsOpen)
|
||||||
|
|
@ -378,7 +410,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the <see cref="ImeWindow"/>.
|
/// Toggles the <see cref="ImeWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ToggleIMEWindow() => this.imeWindow.Toggle();
|
public void ToggleImeWindow() => this.imeWindow.Toggle();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the <see cref="ConsoleWindow"/>.
|
/// Toggles the <see cref="ConsoleWindow"/>.
|
||||||
|
|
@ -504,7 +536,8 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
|
|
||||||
private void DrawCreditsDarkeningAnimation()
|
private void DrawCreditsDarkeningAnimation()
|
||||||
{
|
{
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding | ImGuiStyleVar.WindowBorderSize, 0f);
|
using var style1 = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 0f);
|
||||||
|
using var style2 = ImRaii.PushStyle(ImGuiStyleVar.WindowBorderSize, 0f);
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0));
|
using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0));
|
||||||
|
|
||||||
ImGui.SetNextWindowPos(new Vector2(0, 0));
|
ImGui.SetNextWindowPos(new Vector2(0, 0));
|
||||||
|
|
@ -579,18 +612,16 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
if (ImGui.BeginMainMenuBar())
|
if (ImGui.BeginMainMenuBar())
|
||||||
{
|
{
|
||||||
var dalamud = Service<Dalamud>.Get();
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
|
||||||
if (ImGui.BeginMenu("Dalamud"))
|
if (ImGui.BeginMenu("Dalamud"))
|
||||||
{
|
{
|
||||||
ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu);
|
ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu);
|
||||||
var devBarAtStartup = configuration.DevBarOpenAtStartup;
|
var devBarAtStartup = this.configuration.DevBarOpenAtStartup;
|
||||||
if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup))
|
if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup))
|
||||||
{
|
{
|
||||||
configuration.DevBarOpenAtStartup ^= true;
|
this.configuration.DevBarOpenAtStartup ^= true;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
@ -607,25 +638,25 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel))
|
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel))
|
||||||
{
|
{
|
||||||
EntryPoint.LogLevelSwitch.MinimumLevel = logLevel;
|
EntryPoint.LogLevelSwitch.MinimumLevel = logLevel;
|
||||||
configuration.LogLevel = logLevel;
|
this.configuration.LogLevel = logLevel;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
var logSynchronously = configuration.LogSynchronously;
|
var logSynchronously = this.configuration.LogSynchronously;
|
||||||
if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously))
|
if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously))
|
||||||
{
|
{
|
||||||
configuration.LogSynchronously = logSynchronously;
|
this.configuration.LogSynchronously = logSynchronously;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
|
|
||||||
EntryPoint.InitLogging(
|
EntryPoint.InitLogging(
|
||||||
dalamud.StartInfo.LogPath!,
|
this.dalamud.StartInfo.LogPath!,
|
||||||
dalamud.StartInfo.BootShowConsole,
|
this.dalamud.StartInfo.BootShowConsole,
|
||||||
configuration.LogSynchronously,
|
this.configuration.LogSynchronously,
|
||||||
dalamud.StartInfo.LogName);
|
this.dalamud.StartInfo.LogName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var antiDebug = Service<AntiDebug>.Get();
|
var antiDebug = Service<AntiDebug>.Get();
|
||||||
|
|
@ -637,8 +668,8 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
else
|
else
|
||||||
antiDebug.Disable();
|
antiDebug.Disable();
|
||||||
|
|
||||||
configuration.IsAntiAntiDebugEnabled = newEnabled;
|
this.configuration.IsAntiAntiDebugEnabled = newEnabled;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
@ -730,10 +761,10 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Report crashes at shutdown", null, configuration.ReportShutdownCrashes))
|
if (ImGui.MenuItem("Report crashes at shutdown", null, this.configuration.ReportShutdownCrashes))
|
||||||
{
|
{
|
||||||
configuration.ReportShutdownCrashes = !configuration.ReportShutdownCrashes;
|
this.configuration.ReportShutdownCrashes = !this.configuration.ReportShutdownCrashes;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
@ -744,7 +775,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.MenuItem(Util.AssemblyVersion, false);
|
ImGui.MenuItem(Util.AssemblyVersion, false);
|
||||||
ImGui.MenuItem(dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
|
ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
|
||||||
ImGui.MenuItem($"D: {Util.GetGitHash()}[{Util.GetGitCommitCount()}] CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.Interop.Resolver.Version}]", false);
|
ImGui.MenuItem($"D: {Util.GetGitHash()}[{Util.GetGitCommitCount()}] CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.Interop.Resolver.Version}]", false);
|
||||||
ImGui.MenuItem($"CLR: {Environment.Version}", false);
|
ImGui.MenuItem($"CLR: {Environment.Version}", false);
|
||||||
|
|
||||||
|
|
@ -766,10 +797,10 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
ImGuiManagedAsserts.AssertsEnabled = val;
|
ImGuiManagedAsserts.AssertsEnabled = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup))
|
if (ImGui.MenuItem("Enable asserts at startup", null, this.configuration.AssertsEnabledAtStartup))
|
||||||
{
|
{
|
||||||
configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup;
|
this.configuration.AssertsEnabledAtStartup = !this.configuration.AssertsEnabledAtStartup;
|
||||||
configuration.QueueSave();
|
this.configuration.QueueSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Clear focus"))
|
if (ImGui.MenuItem("Clear focus"))
|
||||||
|
|
@ -779,7 +810,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
|
|
||||||
if (ImGui.MenuItem("Clear stacks"))
|
if (ImGui.MenuItem("Clear stacks"))
|
||||||
{
|
{
|
||||||
Service<InterfaceManager>.Get().ClearStacks();
|
this.interfaceManager.ClearStacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Dump style"))
|
if (ImGui.MenuItem("Dump style"))
|
||||||
|
|
@ -792,7 +823,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
if (propertyInfo.PropertyType == typeof(Vector2))
|
if (propertyInfo.PropertyType == typeof(Vector2))
|
||||||
{
|
{
|
||||||
var vec2 = (Vector2)propertyInfo.GetValue(style);
|
var vec2 = (Vector2)propertyInfo.GetValue(style)!;
|
||||||
info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n";
|
info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -815,9 +846,9 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
Log.Information(info);
|
Log.Information(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.MenuItem("Show dev bar info", null, configuration.ShowDevBarInfo))
|
if (ImGui.MenuItem("Show dev bar info", null, this.configuration.ShowDevBarInfo))
|
||||||
{
|
{
|
||||||
configuration.ShowDevBarInfo = !configuration.ShowDevBarInfo;
|
this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
|
|
@ -827,7 +858,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Replace ExceptionHandler"))
|
if (ImGui.MenuItem("Replace ExceptionHandler"))
|
||||||
{
|
{
|
||||||
dalamud.ReplaceExceptionHandler();
|
this.dalamud.ReplaceExceptionHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
|
|
@ -922,7 +953,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
if (Service<GameGui>.Get().GameUiHidden)
|
if (Service<GameGui>.Get().GameUiHidden)
|
||||||
ImGui.BeginMenu("UI is hidden...", false);
|
ImGui.BeginMenu("UI is hidden...", false);
|
||||||
|
|
||||||
if (configuration.ShowDevBarInfo)
|
if (this.configuration.ShowDevBarInfo)
|
||||||
{
|
{
|
||||||
ImGui.PushFont(InterfaceManager.MonoFont);
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||||
|
|
||||||
|
|
@ -931,9 +962,9 @@ internal class DalamudInterface : IDisposable, IServiceType
|
||||||
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
|
||||||
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
|
||||||
|
|
||||||
var videoMem = Service<InterfaceManager>.Get().GetD3dMemoryInfo();
|
var videoMem = this.interfaceManager.GetD3dMemoryInfo();
|
||||||
ImGui.BeginMenu(
|
ImGui.BeginMenu(
|
||||||
!videoMem.HasValue ? $"V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}",
|
!videoMem.HasValue ? "V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}",
|
||||||
false);
|
false);
|
||||||
|
|
||||||
ImGui.PopFont();
|
ImGui.PopFont();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Interface.Style;
|
using Dalamud.Interface.Style;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Dalamud.Utility.Timing;
|
using Dalamud.Utility.Timing;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -52,8 +53,16 @@ namespace Dalamud.Interface.Internal;
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal class InterfaceManager : IDisposable, IServiceType
|
internal class InterfaceManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
private const float DefaultFontSizePt = 12.0f;
|
/// <summary>
|
||||||
private const float DefaultFontSizePx = DefaultFontSizePt * 4.0f / 3.0f;
|
/// The default font size, in points.
|
||||||
|
/// </summary>
|
||||||
|
public const float DefaultFontSizePt = 12.0f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default font size, in pixels.
|
||||||
|
/// </summary>
|
||||||
|
public const float DefaultFontSizePx = (DefaultFontSizePt * 4.0f) / 3.0f;
|
||||||
|
|
||||||
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
|
private const ushort Fallback1Codepoint = 0x3013; // Geta mark; FFXIV uses this to indicate that a glyph is missing.
|
||||||
private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
|
private const ushort Fallback2Codepoint = '-'; // FFXIV uses dash if Geta mark is unavailable.
|
||||||
|
|
||||||
|
|
@ -1055,10 +1064,15 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceManager.CallWhenServicesReady]
|
[ServiceManager.CallWhenServicesReady]
|
||||||
private void ContinueConstruction(TargetSigScanner sigScanner, Framework framework)
|
private void ContinueConstruction(
|
||||||
|
TargetSigScanner sigScanner,
|
||||||
|
DalamudAssetManager dalamudAssetManager,
|
||||||
|
DalamudConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
dalamudAssetManager.WaitForAllRequiredAssets().Wait();
|
||||||
|
|
||||||
this.address.Setup(sigScanner);
|
this.address.Setup(sigScanner);
|
||||||
framework.RunOnFrameworkThread(() =>
|
this.framework.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
|
@ -1070,7 +1084,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
if (configuration.WindowIsImmersive)
|
||||||
this.SetImmersiveMode(true);
|
this.SetImmersiveMode(true);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
@ -1277,7 +1291,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an instance of InstanceManager with scene ready for use.
|
/// Represents an instance of InstanceManager with scene ready for use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
public class InterfaceManagerWithScene : IServiceType
|
public class InterfaceManagerWithScene : IServiceType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
|
|
@ -32,7 +33,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
";
|
";
|
||||||
|
|
||||||
private readonly TitleScreenMenuWindow tsmWindow;
|
private readonly TitleScreenMenuWindow tsmWindow;
|
||||||
private readonly IDalamudTextureWrap logoTexture;
|
|
||||||
|
|
||||||
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
|
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
|
||||||
{
|
{
|
||||||
|
|
@ -47,6 +47,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
};
|
};
|
||||||
|
|
||||||
private IDalamudTextureWrap? apiBumpExplainerTexture;
|
private IDalamudTextureWrap? apiBumpExplainerTexture;
|
||||||
|
private IDalamudTextureWrap? logoTexture;
|
||||||
private GameFontHandle? bannerFont;
|
private GameFontHandle? bannerFont;
|
||||||
|
|
||||||
private State state = State.WindowFadeIn;
|
private State state = State.WindowFadeIn;
|
||||||
|
|
@ -63,11 +64,9 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
this.tsmWindow = tsmWindow;
|
this.tsmWindow = tsmWindow;
|
||||||
this.Namespace = "DalamudChangelogWindow";
|
this.Namespace = "DalamudChangelogWindow";
|
||||||
|
|
||||||
this.logoTexture = Service<Branding>.Get().Logo;
|
|
||||||
|
|
||||||
// If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
|
// If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
|
||||||
if (WarrantsChangelog())
|
if (WarrantsChangelog())
|
||||||
this.MakeFont();
|
Service<GameFontManager>.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
|
|
@ -98,7 +97,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
|
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
|
||||||
this.tsmWindow.AllowDrawing = false;
|
this.tsmWindow.AllowDrawing = false;
|
||||||
|
|
||||||
this.MakeFont();
|
this.MakeFont(Service<GameFontManager>.Get());
|
||||||
|
|
||||||
this.state = State.WindowFadeIn;
|
this.state = State.WindowFadeIn;
|
||||||
this.windowFade.Reset();
|
this.windowFade.Reset();
|
||||||
|
|
@ -188,6 +187,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
|
|
||||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, Math.Clamp(this.windowFade.EasedPoint.X - 0.5f, 0f, 1f)))
|
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, Math.Clamp(this.windowFade.EasedPoint.X - 0.5f, 0f, 1f)))
|
||||||
{
|
{
|
||||||
|
this.logoTexture ??= Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.Logo);
|
||||||
ImGui.Image(this.logoTexture.ImGuiHandle, logoSize);
|
ImGui.Image(this.logoTexture.ImGuiHandle, logoSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -376,15 +376,8 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.logoTexture.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MakeFont()
|
private void MakeFont(GameFontManager gfm) =>
|
||||||
{
|
this.bannerFont ??= gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.MiedingerMid18));
|
||||||
if (this.bannerFont == null)
|
|
||||||
{
|
|
||||||
var gfm = Service<GameFontManager>.Get();
|
|
||||||
this.bannerFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.MiedingerMid18));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,11 +56,10 @@ internal class ConsoleWindow : Window, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ConsoleWindow()
|
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
|
public ConsoleWindow(DalamudConfiguration configuration)
|
||||||
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
|
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
|
||||||
|
|
||||||
this.autoScroll = configuration.LogAutoScroll;
|
this.autoScroll = configuration.LogAutoScroll;
|
||||||
this.autoOpen = configuration.LogOpenAtStartup;
|
this.autoOpen = configuration.LogOpenAtStartup;
|
||||||
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
SerilogEventSink.Instance.LogLine += this.OnLogLine;
|
||||||
|
|
|
||||||
|
|
@ -18,39 +18,39 @@ internal class DataWindow : Window
|
||||||
{
|
{
|
||||||
private readonly IDataWindowWidget[] modules =
|
private readonly IDataWindowWidget[] modules =
|
||||||
{
|
{
|
||||||
new ServicesWidget(),
|
|
||||||
new AddressesWidget(),
|
|
||||||
new ObjectTableWidget(),
|
|
||||||
new FateTableWidget(),
|
|
||||||
new SeFontTestWidget(),
|
|
||||||
new FontAwesomeTestWidget(),
|
|
||||||
new PartyListWidget(),
|
|
||||||
new BuddyListWidget(),
|
|
||||||
new PluginIpcWidget(),
|
|
||||||
new ConditionWidget(),
|
|
||||||
new GaugeWidget(),
|
|
||||||
new CommandWidget(),
|
|
||||||
new AddonWidget(),
|
|
||||||
new AddonInspectorWidget(),
|
new AddonInspectorWidget(),
|
||||||
|
new AddonLifecycleWidget(),
|
||||||
|
new AddonWidget(),
|
||||||
|
new AddressesWidget(),
|
||||||
|
new AetherytesWidget(),
|
||||||
new AtkArrayDataBrowserWidget(),
|
new AtkArrayDataBrowserWidget(),
|
||||||
|
new BuddyListWidget(),
|
||||||
|
new CommandWidget(),
|
||||||
|
new ConditionWidget(),
|
||||||
|
new ConfigurationWidget(),
|
||||||
|
new DataShareWidget(),
|
||||||
|
new DtrBarWidget(),
|
||||||
|
new FateTableWidget(),
|
||||||
|
new FlyTextWidget(),
|
||||||
|
new FontAwesomeTestWidget(),
|
||||||
|
new GamepadWidget(),
|
||||||
|
new GaugeWidget(),
|
||||||
|
new HookWidget(),
|
||||||
|
new IconBrowserWidget(),
|
||||||
|
new ImGuiWidget(),
|
||||||
|
new KeyStateWidget(),
|
||||||
|
new NetworkMonitorWidget(),
|
||||||
|
new ObjectTableWidget(),
|
||||||
|
new PartyListWidget(),
|
||||||
|
new PluginIpcWidget(),
|
||||||
|
new SeFontTestWidget(),
|
||||||
|
new ServicesWidget(),
|
||||||
new StartInfoWidget(),
|
new StartInfoWidget(),
|
||||||
new TargetWidget(),
|
new TargetWidget(),
|
||||||
new ToastWidget(),
|
|
||||||
new FlyTextWidget(),
|
|
||||||
new ImGuiWidget(),
|
|
||||||
new TexWidget(),
|
|
||||||
new KeyStateWidget(),
|
|
||||||
new GamepadWidget(),
|
|
||||||
new ConfigurationWidget(),
|
|
||||||
new TaskSchedulerWidget(),
|
new TaskSchedulerWidget(),
|
||||||
new HookWidget(),
|
new TexWidget(),
|
||||||
new AetherytesWidget(),
|
new ToastWidget(),
|
||||||
new DtrBarWidget(),
|
|
||||||
new UIColorWidget(),
|
new UIColorWidget(),
|
||||||
new DataShareWidget(),
|
|
||||||
new NetworkMonitorWidget(),
|
|
||||||
new IconBrowserWidget(),
|
|
||||||
new AddonLifecycleWidget(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly IOrderedEnumerable<IDataWindowWidget> orderedModules;
|
private readonly IOrderedEnumerable<IDataWindowWidget> orderedModules;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows;
|
namespace Dalamud.Interface.Internal.Windows.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class representing a date window entry.
|
/// Class representing a date window entry.
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,14 @@ public class AddonLifecycleWidget : IDataWindowWidget
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
this.AddonLifecycle = Service<AddonLifecycle>.GetNullable();
|
Service<AddonLifecycle>
|
||||||
if (this.AddonLifecycle is not null) this.Ready = true;
|
.GetAsync()
|
||||||
|
.ContinueWith(
|
||||||
|
r =>
|
||||||
|
{
|
||||||
|
this.AddonLifecycle = r.Result;
|
||||||
|
this.Ready = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,30 @@ internal class FontAwesomeTestWidget : IDataWindowWidget
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
this.iconCategories ??= FontAwesomeHelpers.GetCategories();
|
this.iconCategories ??= new[] { "(Show All)", "(Undefined)" }
|
||||||
|
.Concat(FontAwesomeHelpers.GetCategories().Skip(1))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
if (this.iconSearchChanged)
|
if (this.iconSearchChanged)
|
||||||
{
|
{
|
||||||
this.icons = FontAwesomeHelpers.SearchIcons(this.iconSearchInput, this.iconCategories[this.selectedIconCategory]);
|
if (this.iconSearchInput == string.Empty && this.selectedIconCategory <= 1)
|
||||||
|
{
|
||||||
|
var en = InterfaceManager.IconFont.GlyphsWrapped()
|
||||||
|
.Select(x => (FontAwesomeIcon)x.Codepoint)
|
||||||
|
.Where(x => (ushort)x is >= 0xE000 and < 0xF000);
|
||||||
|
en = this.selectedIconCategory == 0
|
||||||
|
? en.Concat(FontAwesomeHelpers.SearchIcons(string.Empty, string.Empty))
|
||||||
|
: en.Except(FontAwesomeHelpers.SearchIcons(string.Empty, string.Empty));
|
||||||
|
this.icons = en.Distinct().Order().ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.icons = FontAwesomeHelpers.SearchIcons(
|
||||||
|
this.iconSearchInput,
|
||||||
|
this.selectedIconCategory <= 1 ? string.Empty : this.iconCategories[this.selectedIconCategory]);
|
||||||
|
}
|
||||||
|
|
||||||
this.iconNames = this.icons.Select(icon => Enum.GetName(icon)!).ToList();
|
this.iconNames = this.icons.Select(icon => Enum.GetName(icon)!).ToList();
|
||||||
this.iconSearchChanged = false;
|
this.iconSearchChanged = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
@ -12,8 +11,8 @@ using Dalamud.Networking.Http;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
using Dalamud.Plugin.Internal.Types;
|
||||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiScene;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows;
|
namespace Dalamud.Interface.Internal.Windows;
|
||||||
|
|
@ -47,12 +46,6 @@ internal class PluginImageCache : IDisposable, IServiceType
|
||||||
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
|
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
|
||||||
private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}";
|
private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}";
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly InterfaceManager.InterfaceManagerWithScene imWithScene = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
|
||||||
private readonly Branding branding = Service<Branding>.Get();
|
|
||||||
|
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
||||||
|
|
||||||
|
|
@ -64,35 +57,12 @@ internal class PluginImageCache : IDisposable, IServiceType
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?> pluginIconMap = new();
|
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?> pluginIconMap = new();
|
||||||
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?[]?> pluginImagesMap = new();
|
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?[]?> pluginImagesMap = new();
|
||||||
|
private readonly DalamudAssetManager dalamudAssetManager;
|
||||||
private readonly Task<IDalamudTextureWrap> emptyTextureTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> disabledIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> outdatedInstallableIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> defaultIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> troubleIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> updateIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> installedIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> thirdIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> thirdInstalledIconTask;
|
|
||||||
private readonly Task<IDalamudTextureWrap> corePluginIconTask;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private PluginImageCache(Dalamud dalamud)
|
private PluginImageCache(Dalamud dalamud, DalamudAssetManager dalamudAssetManager)
|
||||||
{
|
{
|
||||||
Task<IDalamudTextureWrap>? TaskWrapIfNonNull(IDalamudTextureWrap? tw) => tw == null ? null : Task.FromResult(tw!);
|
this.dalamudAssetManager = dalamudAssetManager;
|
||||||
var imwst = Task.Run(() => this.imWithScene);
|
|
||||||
|
|
||||||
this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!);
|
|
||||||
this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.outdatedInstallableIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "outdatedInstallableIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.thirdIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.thirdInstalledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
|
||||||
this.corePluginIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(this.branding.LogoSmall)).Unwrap();
|
|
||||||
|
|
||||||
this.downloadTask = Task.Factory.StartNew(
|
this.downloadTask = Task.Factory.StartNew(
|
||||||
() => this.DownloadTask(8), TaskCreationOptions.LongRunning);
|
() => this.DownloadTask(8), TaskCreationOptions.LongRunning);
|
||||||
this.loadTask = Task.Factory.StartNew(
|
this.loadTask = Task.Factory.StartNew(
|
||||||
|
|
@ -102,72 +72,62 @@ internal class PluginImageCache : IDisposable, IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the fallback empty texture.
|
/// Gets the fallback empty texture.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap EmptyTexture => this.emptyTextureTask.IsCompleted
|
public IDalamudTextureWrap EmptyTexture =>
|
||||||
? this.emptyTextureTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
|
||||||
: this.emptyTextureTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the disabled plugin icon.
|
/// Gets the disabled plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap DisabledIcon => this.disabledIconTask.IsCompleted
|
public IDalamudTextureWrap DisabledIcon =>
|
||||||
? this.disabledIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.DisabledIcon, this.EmptyTexture);
|
||||||
: this.disabledIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the outdated installable plugin icon.
|
/// Gets the outdated installable plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap OutdatedInstallableIcon => this.outdatedInstallableIconTask.IsCompleted
|
public IDalamudTextureWrap OutdatedInstallableIcon =>
|
||||||
? this.outdatedInstallableIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.OutdatedInstallableIcon, this.EmptyTexture);
|
||||||
: this.outdatedInstallableIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default plugin icon.
|
/// Gets the default plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap DefaultIcon => this.defaultIconTask.IsCompleted
|
public IDalamudTextureWrap DefaultIcon =>
|
||||||
? this.defaultIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.DefaultIcon, this.EmptyTexture);
|
||||||
: this.defaultIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin trouble icon overlay.
|
/// Gets the plugin trouble icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap TroubleIcon => this.troubleIconTask.IsCompleted
|
public IDalamudTextureWrap TroubleIcon =>
|
||||||
? this.troubleIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TroubleIcon, this.EmptyTexture);
|
||||||
: this.troubleIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin update icon overlay.
|
/// Gets the plugin update icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap UpdateIcon => this.updateIconTask.IsCompleted
|
public IDalamudTextureWrap UpdateIcon =>
|
||||||
? this.updateIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.UpdateIcon, this.EmptyTexture);
|
||||||
: this.updateIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the plugin installed icon overlay.
|
/// Gets the plugin installed icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap InstalledIcon => this.installedIconTask.IsCompleted
|
public IDalamudTextureWrap InstalledIcon =>
|
||||||
? this.installedIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.InstalledIcon, this.EmptyTexture);
|
||||||
: this.installedIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the third party plugin icon overlay.
|
/// Gets the third party plugin icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap ThirdIcon => this.thirdIconTask.IsCompleted
|
public IDalamudTextureWrap ThirdIcon =>
|
||||||
? this.thirdIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.ThirdIcon, this.EmptyTexture);
|
||||||
: this.thirdIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the installed third party plugin icon overlay.
|
/// Gets the installed third party plugin icon overlay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap ThirdInstalledIcon => this.thirdInstalledIconTask.IsCompleted
|
public IDalamudTextureWrap ThirdInstalledIcon =>
|
||||||
? this.thirdInstalledIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.ThirdInstalledIcon, this.EmptyTexture);
|
||||||
: this.thirdInstalledIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the core plugin icon.
|
/// Gets the core plugin icon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IDalamudTextureWrap CorePluginIcon => this.corePluginIconTask.IsCompleted
|
public IDalamudTextureWrap CorePluginIcon =>
|
||||||
? this.corePluginIconTask.Result
|
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture);
|
||||||
: this.corePluginIconTask.GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -185,22 +145,6 @@ internal class PluginImageCache : IDisposable, IServiceType
|
||||||
this.downloadQueue.Dispose();
|
this.downloadQueue.Dispose();
|
||||||
this.loadQueue.Dispose();
|
this.loadQueue.Dispose();
|
||||||
|
|
||||||
foreach (var task in new[]
|
|
||||||
{
|
|
||||||
this.defaultIconTask,
|
|
||||||
this.troubleIconTask,
|
|
||||||
this.updateIconTask,
|
|
||||||
this.installedIconTask,
|
|
||||||
this.thirdIconTask,
|
|
||||||
this.thirdInstalledIconTask,
|
|
||||||
this.corePluginIconTask,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
task.Wait();
|
|
||||||
if (task.IsCompletedSuccessfully)
|
|
||||||
task.Result.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var icon in this.pluginIconMap.Values)
|
foreach (var icon in this.pluginIconMap.Values)
|
||||||
{
|
{
|
||||||
icon?.Dispose();
|
icon?.Dispose();
|
||||||
|
|
@ -319,7 +263,7 @@ internal class PluginImageCache : IDisposable, IServiceType
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var interfaceManager = this.imWithScene.Manager;
|
var interfaceManager = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
|
||||||
var framework = await Service<Framework>.GetAsync();
|
var framework = await Service<Framework>.GetAsync();
|
||||||
|
|
||||||
IDalamudTextureWrap? image;
|
IDalamudTextureWrap? image;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
private string[] testerImagePaths = new string[5];
|
private string[] testerImagePaths = new string[5];
|
||||||
private string testerIconPath = string.Empty;
|
private string testerIconPath = string.Empty;
|
||||||
|
|
||||||
private IDalamudTextureWrap?[] testerImages;
|
private IDalamudTextureWrap?[]? testerImages;
|
||||||
private IDalamudTextureWrap? testerIcon;
|
private IDalamudTextureWrap? testerIcon;
|
||||||
|
|
||||||
private bool testerError = false;
|
private bool testerError = false;
|
||||||
|
|
@ -132,9 +132,10 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
|
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param>
|
/// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param>
|
||||||
public PluginInstallerWindow(PluginImageCache imageCache)
|
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
|
public PluginInstallerWindow(PluginImageCache imageCache, DalamudConfiguration configuration)
|
||||||
: base(
|
: base(
|
||||||
Locs.WindowTitle + (Service<DalamudConfiguration>.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
Locs.WindowTitle + (configuration.DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
||||||
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
||||||
{
|
{
|
||||||
this.IsOpen = true;
|
this.IsOpen = true;
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ internal class PluginStatWindow : Window
|
||||||
{
|
{
|
||||||
var pluginManager = Service<PluginManager>.Get();
|
var pluginManager = Service<PluginManager>.Get();
|
||||||
|
|
||||||
ImGui.BeginTabBar("Stat Tabs");
|
if (!ImGui.BeginTabBar("Stat Tabs"))
|
||||||
|
return;
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Draw times"))
|
if (ImGui.BeginTabItem("Draw times"))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -46,7 +45,7 @@ public class ProfilerWindow : Window
|
||||||
|
|
||||||
ImGui.Text("Timings");
|
ImGui.Text("Timings");
|
||||||
|
|
||||||
var childHeight = Math.Max(300, 20 * (2 + this.occupied.Count));
|
var childHeight = Math.Max(300, 20 * (2.5f + this.occupied.Count));
|
||||||
|
|
||||||
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
|
if (ImGui.BeginChild("Timings", new Vector2(0, childHeight), true))
|
||||||
{
|
{
|
||||||
|
|
@ -115,7 +114,7 @@ public class ProfilerWindow : Window
|
||||||
parentDepthDict[timingHandle.Id] = depth;
|
parentDepthDict[timingHandle.Id] = depth;
|
||||||
|
|
||||||
startX = Math.Max(startX, 0);
|
startX = Math.Max(startX, 0);
|
||||||
endX = Math.Max(endX, 0);
|
endX = Math.Max(endX, startX + (ImGuiHelpers.GlobalScale * 16));
|
||||||
|
|
||||||
Vector4 rectColor;
|
Vector4 rectColor;
|
||||||
if (this.occupied[depth].Count % 2 == 0)
|
if (this.occupied[depth].Count % 2 == 0)
|
||||||
|
|
@ -129,11 +128,6 @@ public class ProfilerWindow : Window
|
||||||
if (maxRectDept < depth)
|
if (maxRectDept < depth)
|
||||||
maxRectDept = (uint)depth;
|
maxRectDept = (uint)depth;
|
||||||
|
|
||||||
if (startX == endX)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var minPos = pos + new Vector2((uint)startX, 20 * depth);
|
var minPos = pos + new Vector2((uint)startX, 20 * depth);
|
||||||
var maxPos = pos + new Vector2((uint)endX, 20 * (depth + 1));
|
var maxPos = pos + new Vector2((uint)endX, 20 * (depth + 1));
|
||||||
|
|
||||||
|
|
@ -231,22 +225,22 @@ public class ProfilerWindow : Window
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
|
|
||||||
var sliderMin = (float)this.min / 1000f;
|
var sliderMin = (float)this.min / 1000f;
|
||||||
if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.1fs"))
|
if (ImGui.SliderFloat("Start", ref sliderMin, (float)actualMin / 1000f, (float)this.max / 1000f, "%.2fs"))
|
||||||
{
|
{
|
||||||
this.min = sliderMin * 1000f;
|
this.min = sliderMin * 1000f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sliderMax = (float)this.max / 1000f;
|
var sliderMax = (float)this.max / 1000f;
|
||||||
if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.1fs"))
|
if (ImGui.SliderFloat("End", ref sliderMax, (float)this.min / 1000f, (float)actualMax / 1000f, "%.2fs"))
|
||||||
{
|
{
|
||||||
this.max = sliderMax * 1000f;
|
this.max = sliderMax * 1000f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizeShown = (float)(this.max - this.min);
|
var sizeShown = (float)(this.max - this.min) / 1000f;
|
||||||
var sizeActual = (float)(actualMax - actualMin);
|
var sizeActual = (float)(actualMax - actualMin) / 1000f;
|
||||||
if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.1fs"))
|
if (ImGui.SliderFloat("Size", ref sizeShown, sizeActual / 10f, sizeActual, "%.2fs"))
|
||||||
{
|
{
|
||||||
this.max = this.min + sizeShown;
|
this.max = this.min + (sizeShown * 1000f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text("Min: " + actualMin.ToString("0.000"));
|
ImGui.Text("Min: " + actualMin.ToString("0.000"));
|
||||||
|
|
@ -257,6 +251,7 @@ public class ProfilerWindow : Window
|
||||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internals")]
|
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internals")]
|
||||||
private class RectInfo
|
private class RectInfo
|
||||||
{
|
{
|
||||||
|
// ReSharper disable once NotNullOrRequiredMemberIsNotInitialized <- well you're wrong
|
||||||
internal TimingHandle Timing;
|
internal TimingHandle Timing;
|
||||||
internal Vector2 MinPos;
|
internal Vector2 MinPos;
|
||||||
internal Vector2 MaxPos;
|
internal Vector2 MaxPos;
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,8 @@ internal class SettingsWindow : Window
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetCursorPos(windowSize - ImGuiHelpers.ScaledVector2(70));
|
ImGui.SetCursorPos(windowSize - ImGuiHelpers.ScaledVector2(70));
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface.GameFonts;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin.Internal;
|
using Dalamud.Plugin.Internal;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -171,19 +172,16 @@ Dalamud is licensed under AGPL v3 or later.
|
||||||
Contribute at: https://github.com/goatcorp/Dalamud
|
Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
";
|
";
|
||||||
|
|
||||||
private readonly IDalamudTextureWrap logoTexture;
|
|
||||||
private readonly Stopwatch creditsThrottler;
|
private readonly Stopwatch creditsThrottler;
|
||||||
|
|
||||||
private string creditsText;
|
private string creditsText;
|
||||||
|
|
||||||
private bool resetNow = false;
|
private bool resetNow = false;
|
||||||
|
private IDalamudTextureWrap? logoTexture;
|
||||||
private GameFontHandle? thankYouFont;
|
private GameFontHandle? thankYouFont;
|
||||||
|
|
||||||
public SettingsTabAbout()
|
public SettingsTabAbout()
|
||||||
{
|
{
|
||||||
var branding = Service<Branding>.Get();
|
|
||||||
|
|
||||||
this.logoTexture = branding.Logo;
|
|
||||||
this.creditsThrottler = new();
|
this.creditsThrottler = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,6 +249,7 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
||||||
|
|
||||||
const float imageSize = 190f;
|
const float imageSize = 190f;
|
||||||
ImGui.SameLine((ImGui.GetWindowWidth() / 2) - (imageSize / 2));
|
ImGui.SameLine((ImGui.GetWindowWidth() / 2) - (imageSize / 2));
|
||||||
|
this.logoTexture ??= Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.Logo);
|
||||||
ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(imageSize));
|
ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(imageSize));
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0, 20f);
|
ImGuiHelpers.ScaledDummy(0, 20f);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
using Dalamud.Configuration.Internal;
|
using Dalamud.Configuration.Internal;
|
||||||
|
|
@ -16,6 +17,16 @@ namespace Dalamud.Interface.Internal.Windows.Settings.Tabs;
|
||||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Internals")]
|
||||||
public class SettingsTabLook : SettingsTab
|
public class SettingsTabLook : SettingsTab
|
||||||
{
|
{
|
||||||
|
private static readonly (string, float)[] GlobalUiScalePresets =
|
||||||
|
{
|
||||||
|
("9.6pt##DalamudSettingsGlobalUiScaleReset96", 9.6f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
("12pt##DalamudSettingsGlobalUiScaleReset12", 12f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
("14pt##DalamudSettingsGlobalUiScaleReset14", 14f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
("18pt##DalamudSettingsGlobalUiScaleReset18", 18f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
("24pt##DalamudSettingsGlobalUiScaleReset24", 24f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
("36pt##DalamudSettingsGlobalUiScaleReset36", 36f / InterfaceManager.DefaultFontSizePt),
|
||||||
|
};
|
||||||
|
|
||||||
private float globalUiScale;
|
private float globalUiScale;
|
||||||
private float fontGamma;
|
private float fontGamma;
|
||||||
|
|
||||||
|
|
@ -135,55 +146,22 @@ public class SettingsTabLook : SettingsTab
|
||||||
{
|
{
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
var interfaceManager = Service<InterfaceManager>.Get();
|
||||||
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global Font Scale"));
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
|
|
||||||
if (ImGui.Button("9.6pt##DalamudSettingsGlobalUiScaleReset96"))
|
|
||||||
{
|
|
||||||
this.globalUiScale = 9.6f / 12.0f;
|
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
|
||||||
interfaceManager.RebuildFonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
var buttonSize =
|
||||||
if (ImGui.Button("12pt##DalamudSettingsGlobalUiScaleReset12"))
|
GlobalUiScalePresets
|
||||||
|
.Select(x => ImGui.CalcTextSize(x.Item1, 0, x.Item1.IndexOf('#')))
|
||||||
|
.Aggregate(Vector2.Zero, Vector2.Max)
|
||||||
|
+ (ImGui.GetStyle().FramePadding * 2);
|
||||||
|
foreach (var (buttonLabel, scale) in GlobalUiScalePresets)
|
||||||
{
|
{
|
||||||
this.globalUiScale = 1.0f;
|
ImGui.SameLine();
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
if (ImGui.Button(buttonLabel, buttonSize) && Math.Abs(this.globalUiScale - scale) > float.Epsilon)
|
||||||
interfaceManager.RebuildFonts();
|
{
|
||||||
}
|
ImGui.GetIO().FontGlobalScale = this.globalUiScale = scale;
|
||||||
|
interfaceManager.RebuildFonts();
|
||||||
ImGui.SameLine();
|
}
|
||||||
if (ImGui.Button("14pt##DalamudSettingsGlobalUiScaleReset14"))
|
|
||||||
{
|
|
||||||
this.globalUiScale = 14.0f / 12.0f;
|
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
|
||||||
interfaceManager.RebuildFonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("18pt##DalamudSettingsGlobalUiScaleReset18"))
|
|
||||||
{
|
|
||||||
this.globalUiScale = 18.0f / 12.0f;
|
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
|
||||||
interfaceManager.RebuildFonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("24pt##DalamudSettingsGlobalUiScaleReset24"))
|
|
||||||
{
|
|
||||||
this.globalUiScale = 24.0f / 12.0f;
|
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
|
||||||
interfaceManager.RebuildFonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("36pt##DalamudSettingsGlobalUiScaleReset36"))
|
|
||||||
{
|
|
||||||
this.globalUiScale = 36.0f / 12.0f;
|
|
||||||
ImGui.GetIO().FontGlobalScale = this.globalUiScale;
|
|
||||||
interfaceManager.RebuildFonts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalUiScaleInPt = 12f * this.globalUiScale;
|
var globalUiScaleInPt = 12f * this.globalUiScale;
|
||||||
|
|
@ -198,10 +176,9 @@ public class SettingsTabLook : SettingsTab
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3);
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
|
ImGui.Text(Loc.Localize("DalamudSettingsFontGamma", "Font Gamma"));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3);
|
|
||||||
if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
|
if (ImGui.Button(Loc.Localize("DalamudSettingsIndividualConfigResetToDefaultValue", "Reset") + "##DalamudSettingsFontGammaReset"))
|
||||||
{
|
{
|
||||||
this.fontGamma = 1.4f;
|
this.fontGamma = 1.4f;
|
||||||
|
|
|
||||||
|
|
@ -211,121 +211,122 @@ public class StyleEditorWindow : Window
|
||||||
|
|
||||||
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables")))
|
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables")))
|
||||||
{
|
{
|
||||||
ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
|
if (ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
|
||||||
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
|
ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f");
|
||||||
|
ImGui.Text("Borders");
|
||||||
|
ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f");
|
||||||
|
ImGui.Text("Rounding");
|
||||||
|
ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f");
|
||||||
|
ImGui.Text("Alignment");
|
||||||
|
ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
|
||||||
|
var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1;
|
||||||
|
if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0"))
|
||||||
|
style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1);
|
||||||
|
ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content.");
|
||||||
|
ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content.");
|
||||||
|
ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGuiComponents.HelpMarker(
|
||||||
|
"Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).");
|
||||||
|
|
||||||
ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f");
|
ImGui.EndChild();
|
||||||
ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f");
|
}
|
||||||
ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f");
|
|
||||||
ImGui.Text("Borders");
|
|
||||||
ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f");
|
|
||||||
ImGui.Text("Rounding");
|
|
||||||
ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f");
|
|
||||||
ImGui.Text("Alignment");
|
|
||||||
ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f");
|
|
||||||
var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1;
|
|
||||||
if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0"))
|
|
||||||
style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1);
|
|
||||||
ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f");
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content.");
|
|
||||||
ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f");
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content.");
|
|
||||||
ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f");
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGuiComponents.HelpMarker(
|
|
||||||
"Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).");
|
|
||||||
ImGui.EndTabItem();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors")))
|
if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors")))
|
||||||
{
|
{
|
||||||
ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground);
|
if (ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground))
|
||||||
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
|
|
||||||
|
|
||||||
if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None))
|
|
||||||
this.alphaFlags = ImGuiColorEditFlags.None;
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview))
|
|
||||||
this.alphaFlags = ImGuiColorEditFlags.AlphaPreview;
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf))
|
|
||||||
this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
ImGuiComponents.HelpMarker(
|
|
||||||
"In the color list:\n" +
|
|
||||||
"Left-click on color square to open color picker,\n" +
|
|
||||||
"Right-click to open edit options menu.");
|
|
||||||
|
|
||||||
foreach (var imGuiCol in Enum.GetValues<ImGuiCol>())
|
|
||||||
{
|
{
|
||||||
if (imGuiCol == ImGuiCol.COUNT)
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5);
|
||||||
continue;
|
|
||||||
|
|
||||||
ImGui.PushID(imGuiCol.ToString());
|
if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None))
|
||||||
|
this.alphaFlags = ImGuiColorEditFlags.None;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview))
|
||||||
|
this.alphaFlags = ImGuiColorEditFlags.AlphaPreview;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf))
|
||||||
|
this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf;
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags);
|
ImGuiComponents.HelpMarker(
|
||||||
|
"In the color list:\n" +
|
||||||
|
"Left-click on color square to open color picker,\n" +
|
||||||
|
"Right-click to open edit options menu.");
|
||||||
|
|
||||||
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
|
foreach (var imGuiCol in Enum.GetValues<ImGuiCol>())
|
||||||
ImGui.TextUnformatted(imGuiCol.ToString());
|
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
|
||||||
{
|
|
||||||
ImGui.PushID(property.Name);
|
|
||||||
|
|
||||||
var colorVal = property.GetValue(workStyle.BuiltInColors);
|
|
||||||
if (colorVal == null)
|
|
||||||
{
|
{
|
||||||
colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors);
|
if (imGuiCol == ImGuiCol.COUNT)
|
||||||
property.SetValue(workStyle.BuiltInColors, colorVal);
|
continue;
|
||||||
|
|
||||||
|
ImGui.PushID(imGuiCol.ToString());
|
||||||
|
|
||||||
|
ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags);
|
||||||
|
|
||||||
|
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
|
||||||
|
ImGui.TextUnformatted(imGuiCol.ToString());
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
var color = (Vector4)colorVal;
|
ImGui.Separator();
|
||||||
|
|
||||||
if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags))
|
foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
{
|
{
|
||||||
property.SetValue(workStyle.BuiltInColors, color);
|
ImGui.PushID(property.Name);
|
||||||
workStyle.BuiltInColors?.Apply();
|
|
||||||
|
var colorVal = property.GetValue(workStyle.BuiltInColors);
|
||||||
|
if (colorVal == null)
|
||||||
|
{
|
||||||
|
colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors);
|
||||||
|
property.SetValue(workStyle.BuiltInColors, colorVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = (Vector4)colorVal;
|
||||||
|
|
||||||
|
if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags))
|
||||||
|
{
|
||||||
|
property.SetValue(workStyle.BuiltInColors, color);
|
||||||
|
workStyle.BuiltInColors?.Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
|
||||||
|
ImGui.TextUnformatted(property.Name);
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine(0.0f, style.ItemInnerSpacing.X);
|
ImGui.EndChild();
|
||||||
ImGui.TextUnformatted(property.Name);
|
|
||||||
|
|
||||||
ImGui.PopID();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
|
|
@ -12,8 +11,9 @@ using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Storage.Assets;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using ImGuiScene;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows;
|
namespace Dalamud.Interface.Internal.Windows;
|
||||||
|
|
||||||
|
|
@ -25,7 +25,13 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
private const float TargetFontSizePt = 18f;
|
private const float TargetFontSizePt = 18f;
|
||||||
private const float TargetFontSizePx = TargetFontSizePt * 4 / 3;
|
private const float TargetFontSizePx = TargetFontSizePt * 4 / 3;
|
||||||
|
|
||||||
private readonly IDalamudTextureWrap shadeTexture;
|
private readonly ClientState clientState;
|
||||||
|
private readonly DalamudConfiguration configuration;
|
||||||
|
private readonly Framework framework;
|
||||||
|
private readonly GameGui gameGui;
|
||||||
|
private readonly TitleScreenMenu titleScreenMenu;
|
||||||
|
|
||||||
|
private readonly Lazy<IDalamudTextureWrap> shadeTexture;
|
||||||
|
|
||||||
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
|
||||||
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
private readonly Dictionary<Guid, InOutQuint> moveEasings = new();
|
||||||
|
|
@ -39,12 +45,30 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
|
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TitleScreenMenuWindow()
|
/// <param name="clientState">An instance of <see cref="ClientState"/>.</param>
|
||||||
|
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||||
|
/// <param name="dalamudAssetManager">An instance of <see cref="DalamudAssetManager"/>.</param>
|
||||||
|
/// <param name="framework">An instance of <see cref="Framework"/>.</param>
|
||||||
|
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
||||||
|
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
||||||
|
public TitleScreenMenuWindow(
|
||||||
|
ClientState clientState,
|
||||||
|
DalamudConfiguration configuration,
|
||||||
|
DalamudAssetManager dalamudAssetManager,
|
||||||
|
Framework framework,
|
||||||
|
GameGui gameGui,
|
||||||
|
TitleScreenMenu titleScreenMenu)
|
||||||
: base(
|
: base(
|
||||||
"TitleScreenMenuOverlay",
|
"TitleScreenMenuOverlay",
|
||||||
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
|
||||||
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
|
||||||
{
|
{
|
||||||
|
this.clientState = clientState;
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.framework = framework;
|
||||||
|
this.gameGui = gameGui;
|
||||||
|
this.titleScreenMenu = titleScreenMenu;
|
||||||
|
|
||||||
this.IsOpen = true;
|
this.IsOpen = true;
|
||||||
this.DisableWindowSounds = true;
|
this.DisableWindowSounds = true;
|
||||||
this.ForceMainWindow = true;
|
this.ForceMainWindow = true;
|
||||||
|
|
@ -53,17 +77,11 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
this.PositionCondition = ImGuiCond.Always;
|
this.PositionCondition = ImGuiCond.Always;
|
||||||
this.RespectCloseHotkey = false;
|
this.RespectCloseHotkey = false;
|
||||||
|
|
||||||
var dalamud = Service<Dalamud>.Get();
|
this.shadeTexture = new(() => dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TitleScreenMenuShade));
|
||||||
var interfaceManager = Service<InterfaceManager>.Get();
|
|
||||||
|
|
||||||
var shadeTex =
|
|
||||||
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png"));
|
|
||||||
this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture.");
|
|
||||||
|
|
||||||
var framework = Service<Framework>.Get();
|
|
||||||
framework.Update += this.FrameworkOnUpdate;
|
framework.Update += this.FrameworkOnUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
{
|
{
|
||||||
Hide,
|
Hide,
|
||||||
|
|
@ -94,9 +112,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
this.shadeTexture.Dispose();
|
this.framework.Update -= this.FrameworkOnUpdate;
|
||||||
var framework = Service<Framework>.Get();
|
|
||||||
framework.Update -= this.FrameworkOnUpdate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -106,17 +122,17 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var scale = ImGui.GetIO().FontGlobalScale;
|
var scale = ImGui.GetIO().FontGlobalScale;
|
||||||
var entries = Service<TitleScreenMenu>.Get().Entries
|
var entries = this.titleScreenMenu.Entries;
|
||||||
.OrderByDescending(x => x.IsInternal)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
switch (this.state)
|
switch (this.state)
|
||||||
{
|
{
|
||||||
case State.Show:
|
case State.Show:
|
||||||
{
|
{
|
||||||
for (var i = 0; i < entries.Count; i++)
|
var i = 0;
|
||||||
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var entry = entries[i];
|
if (!entry.IsShowConditionSatisfied())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing))
|
if (!this.moveEasings.TryGetValue(entry.Id, out var moveEasing))
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +152,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
moveEasing.Update();
|
moveEasing.Update();
|
||||||
|
|
||||||
var finalPos = (i + 1) * this.shadeTexture.Height * scale;
|
var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale;
|
||||||
var pos = moveEasing.Value * finalPos;
|
var pos = moveEasing.Value * finalPos;
|
||||||
|
|
||||||
// FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
|
// FIXME(goat): Sometimes, easings can overshoot and bring things out of alignment.
|
||||||
|
|
@ -150,6 +166,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
var cursor = ImGui.GetCursorPos();
|
var cursor = ImGui.GetCursorPos();
|
||||||
cursor.Y = (float)pos;
|
cursor.Y = (float)pos;
|
||||||
ImGui.SetCursorPos(cursor);
|
ImGui.SetCursorPos(cursor);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
|
if (!ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows |
|
||||||
|
|
@ -182,17 +199,20 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value))
|
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)this.fadeOutEasing.Value))
|
||||||
{
|
{
|
||||||
for (var i = 0; i < entries.Count; i++)
|
var i = 0;
|
||||||
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var entry = entries[i];
|
if (!entry.IsShowConditionSatisfied())
|
||||||
|
continue;
|
||||||
|
|
||||||
var finalPos = (i + 1) * this.shadeTexture.Height * scale;
|
var finalPos = (i + 1) * this.shadeTexture.Value.Height * scale;
|
||||||
|
|
||||||
this.DrawEntry(entry, i != 0, true, i == 0, false, false);
|
this.DrawEntry(entry, i != 0, true, i == 0, false, false);
|
||||||
|
|
||||||
var cursor = ImGui.GetCursorPos();
|
var cursor = ImGui.GetCursorPos();
|
||||||
cursor.Y = finalPos;
|
cursor.Y = finalPos;
|
||||||
ImGui.SetCursorPos(cursor);
|
ImGui.SetCursorPos(cursor);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +286,8 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
|
|
||||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value))
|
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, (float)shadeEasing.Value))
|
||||||
{
|
{
|
||||||
ImGui.Image(this.shadeTexture.ImGuiHandle, new Vector2(this.shadeTexture.Width * scale, this.shadeTexture.Height * scale));
|
var texture = this.shadeTexture.Value;
|
||||||
|
ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height) * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
var isHover = ImGui.IsItemHovered();
|
var isHover = ImGui.IsItemHovered();
|
||||||
|
|
@ -344,7 +365,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
// Drop shadow
|
// Drop shadow
|
||||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF000000))
|
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF000000))
|
||||||
{
|
{
|
||||||
for (int i = 0, i_ = (int)Math.Ceiling(1 * scale); i < i_; i++)
|
for (int i = 0, to = (int)Math.Ceiling(1 * scale); i < to; i++)
|
||||||
{
|
{
|
||||||
ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i));
|
ImGui.SetCursorPos(new Vector2(cursor.X, cursor.Y + i));
|
||||||
ImGui.Text(entry.Name);
|
ImGui.Text(entry.Name);
|
||||||
|
|
@ -367,19 +388,16 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
||||||
return isHover;
|
return isHover;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FrameworkOnUpdate(IFramework framework)
|
private void FrameworkOnUpdate(IFramework unused)
|
||||||
{
|
{
|
||||||
var clientState = Service<ClientState>.Get();
|
this.IsOpen = !this.clientState.IsLoggedIn;
|
||||||
this.IsOpen = !clientState.IsLoggedIn;
|
|
||||||
|
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
if (!this.configuration.ShowTsm)
|
||||||
if (!configuration.ShowTsm)
|
|
||||||
this.IsOpen = false;
|
this.IsOpen = false;
|
||||||
|
|
||||||
var gameGui = Service<GameGui>.Get();
|
var charaSelect = this.gameGui.GetAddonByName("CharaSelect", 1);
|
||||||
var charaSelect = gameGui.GetAddonByName("CharaSelect", 1);
|
var charaMake = this.gameGui.GetAddonByName("CharaMake", 1);
|
||||||
var charaMake = gameGui.GetAddonByName("CharaMake", 1);
|
var titleDcWorldMap = this.gameGui.GetAddonByName("TitleDCWorldMap", 1);
|
||||||
var titleDcWorldMap = gameGui.GetAddonByName("TitleDCWorldMap", 1);
|
|
||||||
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
|
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
|
||||||
this.IsOpen = false;
|
this.IsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ImGuiScene;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Interface;
|
namespace Dalamud.Interface;
|
||||||
|
|
||||||
|
|
@ -23,14 +24,32 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
internal const uint TextureSize = 64;
|
internal const uint TextureSize = 64;
|
||||||
|
|
||||||
private readonly List<TitleScreenMenuEntry> entries = new();
|
private readonly List<TitleScreenMenuEntry> entries = new();
|
||||||
|
private TitleScreenMenuEntry[]? entriesView;
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private TitleScreenMenu()
|
private TitleScreenMenu()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event to be called when the entry list has been changed.
|
||||||
|
/// </summary>
|
||||||
|
internal event Action? EntryListChange;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IReadOnlyList<TitleScreenMenuEntry> Entries => this.entries;
|
public IReadOnlyList<TitleScreenMenuEntry> Entries
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (this.entries)
|
||||||
|
{
|
||||||
|
if (!this.entries.Any())
|
||||||
|
return Array.Empty<TitleScreenMenuEntry>();
|
||||||
|
|
||||||
|
return this.entriesView ??= this.entries.OrderByDescending(x => x.IsInternal).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public TitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered)
|
public TitleScreenMenuEntry AddEntry(string text, IDalamudTextureWrap texture, Action onTriggered)
|
||||||
|
|
@ -40,19 +59,23 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
throw new ArgumentException("Texture must be 64x64");
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TitleScreenMenuEntry entry;
|
||||||
lock (this.entries)
|
lock (this.entries)
|
||||||
{
|
{
|
||||||
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == Assembly.GetCallingAssembly()).ToList();
|
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == Assembly.GetCallingAssembly()).ToList();
|
||||||
var priority = entriesOfAssembly.Any()
|
var priority = entriesOfAssembly.Any()
|
||||||
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
||||||
: 0;
|
: 0;
|
||||||
var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
entry = new(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
||||||
var i = this.entries.BinarySearch(entry);
|
var i = this.entries.BinarySearch(entry);
|
||||||
if (i < 0)
|
if (i < 0)
|
||||||
i = ~i;
|
i = ~i;
|
||||||
this.entries.Insert(i, entry);
|
this.entries.Insert(i, entry);
|
||||||
return entry;
|
this.entriesView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.EntryListChange?.InvokeSafely();
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -63,15 +86,19 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
throw new ArgumentException("Texture must be 64x64");
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TitleScreenMenuEntry entry;
|
||||||
lock (this.entries)
|
lock (this.entries)
|
||||||
{
|
{
|
||||||
var entry = new TitleScreenMenuEntry(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
entry = new(Assembly.GetCallingAssembly(), priority, text, texture, onTriggered);
|
||||||
var i = this.entries.BinarySearch(entry);
|
var i = this.entries.BinarySearch(entry);
|
||||||
if (i < 0)
|
if (i < 0)
|
||||||
i = ~i;
|
i = ~i;
|
||||||
this.entries.Insert(i, entry);
|
this.entries.Insert(i, entry);
|
||||||
return entry;
|
this.entriesView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.EntryListChange?.InvokeSafely();
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -80,7 +107,10 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
lock (this.entries)
|
lock (this.entries)
|
||||||
{
|
{
|
||||||
this.entries.Remove(entry);
|
this.entries.Remove(entry);
|
||||||
|
this.entriesView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.EntryListChange?.InvokeSafely();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -99,15 +129,19 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
throw new ArgumentException("Texture must be 64x64");
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TitleScreenMenuEntry entry;
|
||||||
lock (this.entries)
|
lock (this.entries)
|
||||||
{
|
{
|
||||||
var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered)
|
entry = new(null, priority, text, texture, onTriggered)
|
||||||
{
|
{
|
||||||
IsInternal = true,
|
IsInternal = true,
|
||||||
};
|
};
|
||||||
this.entries.Add(entry);
|
this.entries.Add(entry);
|
||||||
return entry;
|
this.entriesView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.EntryListChange?.InvokeSafely();
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -116,28 +150,37 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
|
||||||
/// <param name="text">The text to show.</param>
|
/// <param name="text">The text to show.</param>
|
||||||
/// <param name="texture">The texture to show.</param>
|
/// <param name="texture">The texture to show.</param>
|
||||||
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
|
/// <param name="showConditionKeys">The keys that have to be held to display the menu.</param>
|
||||||
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
|
/// <returns>A <see cref="TitleScreenMenu"/> object that can be used to manage the entry.</returns>
|
||||||
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
|
/// <exception cref="ArgumentException">Thrown when the texture provided does not match the required resolution(64x64).</exception>
|
||||||
internal TitleScreenMenuEntry AddEntryCore(string text, IDalamudTextureWrap texture, Action onTriggered)
|
internal TitleScreenMenuEntry AddEntryCore(
|
||||||
|
string text,
|
||||||
|
IDalamudTextureWrap texture,
|
||||||
|
Action onTriggered,
|
||||||
|
params VirtualKey[] showConditionKeys)
|
||||||
{
|
{
|
||||||
if (texture.Height != TextureSize || texture.Width != TextureSize)
|
if (texture.Height != TextureSize || texture.Width != TextureSize)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Texture must be 64x64");
|
throw new ArgumentException("Texture must be 64x64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TitleScreenMenuEntry entry;
|
||||||
lock (this.entries)
|
lock (this.entries)
|
||||||
{
|
{
|
||||||
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == null).ToList();
|
var entriesOfAssembly = this.entries.Where(x => x.CallingAssembly == null).ToList();
|
||||||
var priority = entriesOfAssembly.Any()
|
var priority = entriesOfAssembly.Any()
|
||||||
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
? unchecked(entriesOfAssembly.Select(x => x.Priority).Max() + 1)
|
||||||
: 0;
|
: 0;
|
||||||
var entry = new TitleScreenMenuEntry(null, priority, text, texture, onTriggered)
|
entry = new(null, priority, text, texture, onTriggered, showConditionKeys)
|
||||||
{
|
{
|
||||||
IsInternal = true,
|
IsInternal = true,
|
||||||
};
|
};
|
||||||
this.entries.Add(entry);
|
this.entries.Add(entry);
|
||||||
return entry;
|
this.entriesView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.EntryListChange?.InvokeSafely();
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
using System.Reflection;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
|
|
||||||
namespace Dalamud.Interface;
|
namespace Dalamud.Interface;
|
||||||
|
|
@ -19,13 +23,21 @@ public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
|
||||||
/// <param name="text">The text to show.</param>
|
/// <param name="text">The text to show.</param>
|
||||||
/// <param name="texture">The texture to show.</param>
|
/// <param name="texture">The texture to show.</param>
|
||||||
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
/// <param name="onTriggered">The action to execute when the option is selected.</param>
|
||||||
internal TitleScreenMenuEntry(Assembly? callingAssembly, ulong priority, string text, IDalamudTextureWrap texture, Action onTriggered)
|
/// <param name="showConditionKeys">The keys that have to be held to display the menu.</param>
|
||||||
|
internal TitleScreenMenuEntry(
|
||||||
|
Assembly? callingAssembly,
|
||||||
|
ulong priority,
|
||||||
|
string text,
|
||||||
|
IDalamudTextureWrap texture,
|
||||||
|
Action onTriggered,
|
||||||
|
IEnumerable<VirtualKey>? showConditionKeys = null)
|
||||||
{
|
{
|
||||||
this.CallingAssembly = callingAssembly;
|
this.CallingAssembly = callingAssembly;
|
||||||
this.Priority = priority;
|
this.Priority = priority;
|
||||||
this.Name = text;
|
this.Name = text;
|
||||||
this.Texture = texture;
|
this.Texture = texture;
|
||||||
this.onTriggered = onTriggered;
|
this.onTriggered = onTriggered;
|
||||||
|
this.ShowConditionKeys = (showConditionKeys ?? Array.Empty<VirtualKey>()).ToImmutableSortedSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -58,6 +70,11 @@ public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Guid Id { get; init; } = Guid.NewGuid();
|
internal Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the keys that have to be pressed to show the menu.
|
||||||
|
/// </summary>
|
||||||
|
internal IReadOnlySet<VirtualKey> ShowConditionKeys { get; init; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int CompareTo(TitleScreenMenuEntry? other)
|
public int CompareTo(TitleScreenMenuEntry? other)
|
||||||
{
|
{
|
||||||
|
|
@ -84,6 +101,13 @@ public class TitleScreenMenuEntry : IComparable<TitleScreenMenuEntry>
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines the displaying condition of this menu entry is met.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if met.</returns>
|
||||||
|
internal bool IsShowConditionSatisfied() =>
|
||||||
|
this.ShowConditionKeys.All(x => Service<KeyState>.GetNullable()?[x] is true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trigger the action associated with this entry.
|
/// Trigger the action associated with this entry.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
using Dalamud.Configuration.Internal;
|
||||||
using Dalamud.Game.ClientState.Keys;
|
using Dalamud.Game.ClientState.Keys;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -25,6 +26,20 @@ public static class ImGuiHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static float GlobalScale { get; private set; }
|
public static float GlobalScale { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether ImGui is initialized and ready for use.<br />
|
||||||
|
/// This does not necessarily mean you can call drawing functions.
|
||||||
|
/// </summary>
|
||||||
|
public static unsafe bool IsImGuiInitialized =>
|
||||||
|
ImGui.GetCurrentContext() is not 0 && ImGui.GetIO().NativePtr is not null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the global Dalamud scale; even available before drawing is ready.<br />
|
||||||
|
/// If you are sure that drawing is ready, at the point of using this, use <see cref="GlobalScale"/> instead.
|
||||||
|
/// </summary>
|
||||||
|
public static float GlobalScaleSafe =>
|
||||||
|
IsImGuiInitialized ? ImGui.GetIO().FontGlobalScale : Service<DalamudConfiguration>.Get().GlobalUiScale;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the current ImGui window is on the main viewport.
|
/// Check if the current ImGui window is on the main viewport.
|
||||||
/// Only valid within a window.
|
/// Only valid within a window.
|
||||||
|
|
@ -174,6 +189,47 @@ public static class ImGuiHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unscales fonts after they have been rendered onto atlas.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fontPtr">Font to scale.</param>
|
||||||
|
/// <param name="scale">Scale.</param>
|
||||||
|
/// <param name="round">If a positive number is given, numbers will be rounded to this.</param>
|
||||||
|
public static unsafe void AdjustGlyphMetrics(this ImFontPtr fontPtr, float scale, float round = 0f)
|
||||||
|
{
|
||||||
|
Func<float, float> rounder = round > 0 ? x => MathF.Round(x * round) / round : x => x;
|
||||||
|
|
||||||
|
var font = fontPtr.NativePtr;
|
||||||
|
font->FontSize = rounder(font->FontSize * scale);
|
||||||
|
font->Ascent = rounder(font->Ascent * scale);
|
||||||
|
font->Descent = font->FontSize - font->Ascent;
|
||||||
|
if (font->ConfigData != null)
|
||||||
|
font->ConfigData->SizePixels = rounder(font->ConfigData->SizePixels * scale);
|
||||||
|
|
||||||
|
foreach (ref var glyphHotDataReal in new Span<ImFontGlyphHotDataReal>(
|
||||||
|
(void*)font->IndexedHotData.Data,
|
||||||
|
font->IndexedHotData.Size))
|
||||||
|
{
|
||||||
|
glyphHotDataReal.AdvanceX = rounder(glyphHotDataReal.AdvanceX * scale);
|
||||||
|
glyphHotDataReal.OccupiedWidth = rounder(glyphHotDataReal.OccupiedWidth * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ref var glyphReal in new Span<ImFontGlyphReal>((void*)font->Glyphs.Data, font->Glyphs.Size))
|
||||||
|
{
|
||||||
|
glyphReal.X0 *= scale;
|
||||||
|
glyphReal.X1 *= scale;
|
||||||
|
glyphReal.Y0 *= scale;
|
||||||
|
glyphReal.Y1 *= scale;
|
||||||
|
glyphReal.AdvanceX = rounder(glyphReal.AdvanceX * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ref var kp in new Span<ImFontKerningPair>((void*)font->KerningPairs.Data, font->KerningPairs.Size))
|
||||||
|
kp.AdvanceXAdjustment = rounder(kp.AdvanceXAdjustment * scale);
|
||||||
|
|
||||||
|
foreach (ref var fkp in new Span<float>((void*)font->FrequentKerningPairs.Data, font->FrequentKerningPairs.Size))
|
||||||
|
fkp = rounder(fkp * scale);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fills missing glyphs in target font from source font, if both are not null.
|
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -183,71 +239,110 @@ public static class ImGuiHelpers
|
||||||
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
||||||
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
||||||
public static void CopyGlyphsAcrossFonts(ImFontPtr? source, ImFontPtr? target, bool missingOnly, bool rebuildLookupTable, int rangeLow = 32, int rangeHigh = 0xFFFE)
|
[Obsolete("Use the non-nullable variant.", true)]
|
||||||
|
public static void CopyGlyphsAcrossFonts(
|
||||||
|
ImFontPtr? source,
|
||||||
|
ImFontPtr? target,
|
||||||
|
bool missingOnly,
|
||||||
|
bool rebuildLookupTable = true,
|
||||||
|
int rangeLow = 32,
|
||||||
|
int rangeHigh = 0xFFFE) =>
|
||||||
|
CopyGlyphsAcrossFonts(
|
||||||
|
source ?? default,
|
||||||
|
target ?? default,
|
||||||
|
missingOnly,
|
||||||
|
rebuildLookupTable,
|
||||||
|
rangeLow,
|
||||||
|
rangeHigh);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills missing glyphs in target font from source font, if both are not null.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Source font.</param>
|
||||||
|
/// <param name="target">Target font.</param>
|
||||||
|
/// <param name="missingOnly">Whether to copy missing glyphs only.</param>
|
||||||
|
/// <param name="rebuildLookupTable">Whether to call target.BuildLookupTable().</param>
|
||||||
|
/// <param name="rangeLow">Low codepoint range to copy.</param>
|
||||||
|
/// <param name="rangeHigh">High codepoing range to copy.</param>
|
||||||
|
public static unsafe void CopyGlyphsAcrossFonts(
|
||||||
|
ImFontPtr source,
|
||||||
|
ImFontPtr target,
|
||||||
|
bool missingOnly,
|
||||||
|
bool rebuildLookupTable = true,
|
||||||
|
int rangeLow = 32,
|
||||||
|
int rangeHigh = 0xFFFE)
|
||||||
{
|
{
|
||||||
if (!source.HasValue || !target.HasValue)
|
if (!source.IsNotNullAndLoaded() || !target.IsNotNullAndLoaded())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var scale = target.Value!.FontSize / source.Value!.FontSize;
|
var changed = false;
|
||||||
|
var scale = target.FontSize / source.FontSize;
|
||||||
var addedCodepoints = new HashSet<int>();
|
var addedCodepoints = new HashSet<int>();
|
||||||
unsafe
|
|
||||||
|
if (source.Glyphs.Size == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var glyphs = (ImFontGlyphReal*)source.Glyphs.Data;
|
||||||
|
if (glyphs is null)
|
||||||
|
throw new InvalidOperationException("Glyphs data is empty but size is >0?");
|
||||||
|
|
||||||
|
for (int j = 0, k = source.Glyphs.Size; j < k; j++)
|
||||||
{
|
{
|
||||||
var glyphs = (ImFontGlyphReal*)source.Value!.Glyphs.Data;
|
var glyph = &glyphs![j];
|
||||||
for (int j = 0, k = source.Value!.Glyphs.Size; j < k; j++)
|
if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var prevGlyphPtr = (ImFontGlyphReal*)target.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
|
||||||
|
if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Debug.Assert(glyphs != null, nameof(glyphs) + " != null");
|
addedCodepoints.Add(glyph->Codepoint);
|
||||||
|
target.AddGlyph(
|
||||||
var glyph = &glyphs[j];
|
target.ConfigData,
|
||||||
if (glyph->Codepoint < rangeLow || glyph->Codepoint > rangeHigh)
|
(ushort)glyph->Codepoint,
|
||||||
continue;
|
glyph->TextureIndex,
|
||||||
|
glyph->X0 * scale,
|
||||||
var prevGlyphPtr = (ImFontGlyphReal*)target.Value!.FindGlyphNoFallback((ushort)glyph->Codepoint).NativePtr;
|
((glyph->Y0 - source.Ascent) * scale) + target.Ascent,
|
||||||
if ((IntPtr)prevGlyphPtr == IntPtr.Zero)
|
glyph->X1 * scale,
|
||||||
{
|
((glyph->Y1 - source.Ascent) * scale) + target.Ascent,
|
||||||
addedCodepoints.Add(glyph->Codepoint);
|
glyph->U0,
|
||||||
target.Value!.AddGlyph(
|
glyph->V0,
|
||||||
target.Value!.ConfigData,
|
glyph->U1,
|
||||||
(ushort)glyph->Codepoint,
|
glyph->V1,
|
||||||
glyph->TextureIndex,
|
glyph->AdvanceX * scale);
|
||||||
glyph->X0 * scale,
|
changed = true;
|
||||||
((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
|
||||||
glyph->X1 * scale,
|
|
||||||
((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent,
|
|
||||||
glyph->U0,
|
|
||||||
glyph->V0,
|
|
||||||
glyph->U1,
|
|
||||||
glyph->V1,
|
|
||||||
glyph->AdvanceX * scale);
|
|
||||||
}
|
|
||||||
else if (!missingOnly)
|
|
||||||
{
|
|
||||||
addedCodepoints.Add(glyph->Codepoint);
|
|
||||||
prevGlyphPtr->TextureIndex = glyph->TextureIndex;
|
|
||||||
prevGlyphPtr->X0 = glyph->X0 * scale;
|
|
||||||
prevGlyphPtr->Y0 = ((glyph->Y0 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
|
||||||
prevGlyphPtr->X1 = glyph->X1 * scale;
|
|
||||||
prevGlyphPtr->Y1 = ((glyph->Y1 - source.Value!.Ascent) * scale) + target.Value!.Ascent;
|
|
||||||
prevGlyphPtr->U0 = glyph->U0;
|
|
||||||
prevGlyphPtr->V0 = glyph->V0;
|
|
||||||
prevGlyphPtr->U1 = glyph->U1;
|
|
||||||
prevGlyphPtr->V1 = glyph->V1;
|
|
||||||
prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (!missingOnly)
|
||||||
var kernPairs = source.Value!.KerningPairs;
|
|
||||||
for (int j = 0, k = kernPairs.Size; j < k; j++)
|
|
||||||
{
|
{
|
||||||
if (!addedCodepoints.Contains(kernPairs[j].Left))
|
addedCodepoints.Add(glyph->Codepoint);
|
||||||
continue;
|
prevGlyphPtr->TextureIndex = glyph->TextureIndex;
|
||||||
if (!addedCodepoints.Contains(kernPairs[j].Right))
|
prevGlyphPtr->X0 = glyph->X0 * scale;
|
||||||
continue;
|
prevGlyphPtr->Y0 = ((glyph->Y0 - source.Ascent) * scale) + target.Ascent;
|
||||||
target.Value.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment);
|
prevGlyphPtr->X1 = glyph->X1 * scale;
|
||||||
|
prevGlyphPtr->Y1 = ((glyph->Y1 - source.Ascent) * scale) + target.Ascent;
|
||||||
|
prevGlyphPtr->U0 = glyph->U0;
|
||||||
|
prevGlyphPtr->V0 = glyph->V0;
|
||||||
|
prevGlyphPtr->U1 = glyph->U1;
|
||||||
|
prevGlyphPtr->V1 = glyph->V1;
|
||||||
|
prevGlyphPtr->AdvanceX = glyph->AdvanceX * scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rebuildLookupTable && target.Value!.Glyphs.Size > 0)
|
if (target.Glyphs.Size == 0)
|
||||||
target.Value!.BuildLookupTableNonstandard();
|
return;
|
||||||
|
|
||||||
|
var kernPairs = source.KerningPairs;
|
||||||
|
for (int j = 0, k = kernPairs.Size; j < k; j++)
|
||||||
|
{
|
||||||
|
if (!addedCodepoints.Contains(kernPairs[j].Left))
|
||||||
|
continue;
|
||||||
|
if (!addedCodepoints.Contains(kernPairs[j].Right))
|
||||||
|
continue;
|
||||||
|
target.AddKerningPair(kernPairs[j].Left, kernPairs[j].Right, kernPairs[j].AdvanceXAdjustment);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed && rebuildLookupTable)
|
||||||
|
target.BuildLookupTableNonstandard();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -302,21 +397,35 @@ public static class ImGuiHelpers
|
||||||
/// Center the ImGui cursor for a certain text.
|
/// Center the ImGui cursor for a certain text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">The text to center for.</param>
|
/// <param name="text">The text to center for.</param>
|
||||||
public static void CenterCursorForText(string text)
|
public static void CenterCursorForText(string text) => CenterCursorFor(ImGui.CalcTextSize(text).X);
|
||||||
{
|
|
||||||
var textWidth = ImGui.CalcTextSize(text).X;
|
|
||||||
CenterCursorFor((int)textWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Center the ImGui cursor for an item with a certain width.
|
/// Center the ImGui cursor for an item with a certain width.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="itemWidth">The width to center for.</param>
|
/// <param name="itemWidth">The width to center for.</param>
|
||||||
public static void CenterCursorFor(int itemWidth)
|
public static void CenterCursorFor(float itemWidth) =>
|
||||||
{
|
ImGui.SetCursorPosX((int)((ImGui.GetWindowWidth() - itemWidth) / 2));
|
||||||
var window = (int)ImGui.GetWindowWidth();
|
|
||||||
ImGui.SetCursorPosX((window / 2) - (itemWidth / 2));
|
/// <summary>
|
||||||
}
|
/// Determines whether <paramref name="ptr"/> is empty.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer.</param>
|
||||||
|
/// <returns>Whether it is empty.</returns>
|
||||||
|
public static unsafe bool IsNull(this ImFontPtr ptr) => ptr.NativePtr == null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether <paramref name="ptr"/> is not null and loaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer.</param>
|
||||||
|
/// <returns>Whether it is empty.</returns>
|
||||||
|
public static unsafe bool IsNotNullAndLoaded(this ImFontPtr ptr) => ptr.NativePtr != null && ptr.IsLoaded();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether <paramref name="ptr"/> is empty.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer.</param>
|
||||||
|
/// <returns>Whether it is empty.</returns>
|
||||||
|
public static unsafe bool IsNull(this ImFontAtlasPtr ptr) => ptr.NativePtr == null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get data needed for each new frame.
|
/// Get data needed for each new frame.
|
||||||
|
|
@ -330,19 +439,57 @@ public static class ImGuiHelpers
|
||||||
/// ImFontGlyph the correct version.
|
/// ImFontGlyph the correct version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 40)]
|
||||||
public struct ImFontGlyphReal
|
public struct ImFontGlyphReal
|
||||||
{
|
{
|
||||||
|
[FieldOffset(0)]
|
||||||
public uint ColoredVisibleTextureIndexCodepoint;
|
public uint ColoredVisibleTextureIndexCodepoint;
|
||||||
|
|
||||||
|
[FieldOffset(4)]
|
||||||
public float AdvanceX;
|
public float AdvanceX;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
public float X0;
|
public float X0;
|
||||||
|
|
||||||
|
[FieldOffset(12)]
|
||||||
public float Y0;
|
public float Y0;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
public float X1;
|
public float X1;
|
||||||
|
|
||||||
|
[FieldOffset(20)]
|
||||||
public float Y1;
|
public float Y1;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
public float U0;
|
public float U0;
|
||||||
|
|
||||||
|
[FieldOffset(28)]
|
||||||
public float V0;
|
public float V0;
|
||||||
|
|
||||||
|
[FieldOffset(32)]
|
||||||
public float U1;
|
public float U1;
|
||||||
|
|
||||||
|
[FieldOffset(36)]
|
||||||
public float V1;
|
public float V1;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public Vector2 XY0;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
public Vector2 XY1;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
|
public Vector2 UV0;
|
||||||
|
|
||||||
|
[FieldOffset(32)]
|
||||||
|
public Vector2 UV1;
|
||||||
|
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public Vector4 XY;
|
||||||
|
|
||||||
|
[FieldOffset(24)]
|
||||||
|
public Vector4 UV;
|
||||||
|
|
||||||
private const uint ColoredMask /*****/ = 0b_00000000_00000000_00000000_00000001u;
|
private const uint ColoredMask /*****/ = 0b_00000000_00000000_00000000_00000001u;
|
||||||
private const uint VisibleMask /*****/ = 0b_00000000_00000000_00000000_00000010u;
|
private const uint VisibleMask /*****/ = 0b_00000000_00000000_00000000_00000010u;
|
||||||
private const uint TextureMask /*****/ = 0b_00000000_00000000_00000111_11111100u;
|
private const uint TextureMask /*****/ = 0b_00000000_00000000_00000111_11111100u;
|
||||||
|
|
@ -390,7 +537,7 @@ public static class ImGuiHelpers
|
||||||
|
|
||||||
private const uint UseBisectMask /***/ = 0b_00000000_00000000_00000000_00000001u;
|
private const uint UseBisectMask /***/ = 0b_00000000_00000000_00000000_00000001u;
|
||||||
private const uint OffsetMask /******/ = 0b_00000000_00001111_11111111_11111110u;
|
private const uint OffsetMask /******/ = 0b_00000000_00001111_11111111_11111110u;
|
||||||
private const uint CountMask /*******/ = 0b_11111111_11110000_00000111_11111100u;
|
private const uint CountMask /*******/ = 0b_11111111_11110000_00000000_00000000u;
|
||||||
|
|
||||||
private const int UseBisectShift = 0;
|
private const int UseBisectShift = 0;
|
||||||
private const int OffsetShift = 1;
|
private const int OffsetShift = 1;
|
||||||
|
|
@ -419,6 +566,7 @@ public static class ImGuiHelpers
|
||||||
/// ImFontAtlasCustomRect the correct version.
|
/// ImFontAtlasCustomRect the correct version.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
|
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "ImGui internals")]
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public unsafe struct ImFontAtlasCustomRectReal
|
public unsafe struct ImFontAtlasCustomRectReal
|
||||||
{
|
{
|
||||||
public ushort Width;
|
public ushort Width;
|
||||||
|
|
@ -431,10 +579,10 @@ public static class ImGuiHelpers
|
||||||
public ImFont* Font;
|
public ImFont* Font;
|
||||||
|
|
||||||
private const uint TextureIndexMask /***/ = 0b_00000000_00000000_00000111_11111100u;
|
private const uint TextureIndexMask /***/ = 0b_00000000_00000000_00000111_11111100u;
|
||||||
private const uint GlyphIDMask /********/ = 0b_11111111_11111111_11111000_00000000u;
|
private const uint GlyphIdMask /********/ = 0b_11111111_11111111_11111000_00000000u;
|
||||||
|
|
||||||
private const int TextureIndexShift = 2;
|
private const int TextureIndexShift = 2;
|
||||||
private const int GlyphIDShift = 11;
|
private const int GlyphIdShift = 11;
|
||||||
|
|
||||||
public int TextureIndex
|
public int TextureIndex
|
||||||
{
|
{
|
||||||
|
|
@ -444,8 +592,8 @@ public static class ImGuiHelpers
|
||||||
|
|
||||||
public int GlyphId
|
public int GlyphId
|
||||||
{
|
{
|
||||||
get => (int)(this.TextureIndexAndGlyphId & GlyphIDMask) >> GlyphIDShift;
|
get => (int)(this.TextureIndexAndGlyphId & GlyphIdMask) >> GlyphIdShift;
|
||||||
set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIDMask) | ((uint)value << GlyphIDShift);
|
set => this.TextureIndexAndGlyphId = (this.TextureIndexAndGlyphId & ~GlyphIdMask) | ((uint)value << GlyphIdShift);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
687
Dalamud/Interface/Utility/ImVectorWrapper.cs
Normal file
687
Dalamud/Interface/Utility/ImVectorWrapper.cs
Normal file
|
|
@ -0,0 +1,687 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Utility;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility methods for <see cref="ImVectorWrapper{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ImVectorWrapper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||||
|
/// <paramref name="sourceEnumerable"/>.<br />
|
||||||
|
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The item type.</typeparam>
|
||||||
|
/// <param name="sourceEnumerable">The initial data.</param>
|
||||||
|
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||||
|
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||||
|
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||||
|
public static ImVectorWrapper<T> CreateFromEnumerable<T>(
|
||||||
|
IEnumerable<T> sourceEnumerable,
|
||||||
|
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||||
|
int minCapacity = 0)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var res = new ImVectorWrapper<T>(0, destroyer);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (sourceEnumerable)
|
||||||
|
{
|
||||||
|
case T[] c:
|
||||||
|
res.SetCapacity(Math.Max(minCapacity, c.Length + 1));
|
||||||
|
res.LengthUnsafe = c.Length;
|
||||||
|
c.AsSpan().CopyTo(res.DataSpan);
|
||||||
|
break;
|
||||||
|
case ICollection c:
|
||||||
|
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||||
|
res.AddRange(sourceEnumerable);
|
||||||
|
break;
|
||||||
|
case ICollection<T> c:
|
||||||
|
res.SetCapacity(Math.Max(minCapacity, c.Count + 1));
|
||||||
|
res.AddRange(sourceEnumerable);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.SetCapacity(minCapacity);
|
||||||
|
res.AddRange(sourceEnumerable);
|
||||||
|
res.EnsureCapacity(res.LengthUnsafe + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Null termination
|
||||||
|
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||||
|
res.StorageSpan[res.LengthUnsafe] = default;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
res.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ImVectorWrapper{T}"/> struct, initialized with
|
||||||
|
/// <paramref name="sourceSpan"/>.<br />
|
||||||
|
/// You must call <see cref="ImVectorWrapper{T}.Dispose"/> after use.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The item type.</typeparam>
|
||||||
|
/// <param name="sourceSpan">The initial data.</param>
|
||||||
|
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||||
|
/// <param name="minCapacity">The minimum capacity of the new vector.</param>
|
||||||
|
/// <returns>The new wrapped vector, that has to be disposed after use.</returns>
|
||||||
|
public static ImVectorWrapper<T> CreateFromSpan<T>(
|
||||||
|
ReadOnlySpan<T> sourceSpan,
|
||||||
|
ImVectorWrapper<T>.ImGuiNativeDestroyDelegate? destroyer = null,
|
||||||
|
int minCapacity = 0)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var res = new ImVectorWrapper<T>(Math.Max(minCapacity, sourceSpan.Length + 1), destroyer);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
res.LengthUnsafe = sourceSpan.Length;
|
||||||
|
sourceSpan.CopyTo(res.DataSpan);
|
||||||
|
|
||||||
|
// Null termination
|
||||||
|
Debug.Assert(res.LengthUnsafe < res.CapacityUnsafe, "Capacity must be more than source length + 1");
|
||||||
|
res.StorageSpan[res.LengthUnsafe] = default;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
res.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFontAtlas.ConfigData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ImFontConfig> ConfigDataWrapped(this ImFontAtlasPtr obj) =>
|
||||||
|
obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->ConfigData, ImGuiNative.ImFontConfig_destroy);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFontAtlas.Fonts"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ImFontPtr> FontsWrapped(this ImFontAtlasPtr obj) =>
|
||||||
|
obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->Fonts, x => ImGuiNative.ImFont_destroy(x->NativePtr));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFontAtlas.Textures"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ImFontAtlasTexture> TexturesWrapped(this ImFontAtlasPtr obj) =>
|
||||||
|
obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->Textures);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFont.Glyphs"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphReal> GlyphsWrapped(this ImFontPtr obj) =>
|
||||||
|
obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->Glyphs);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFont.IndexedHotData"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ImGuiHelpers.ImFontGlyphHotDataReal> IndexedHotDataWrapped(this ImFontPtr obj)
|
||||||
|
=> obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->IndexedHotData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps <see cref="ImFont.IndexLookup"/> into a <see cref="ImVectorWrapper{T}"/>.<br />
|
||||||
|
/// This does not need to be disposed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The owner object.</param>
|
||||||
|
/// <returns>The wrapped vector.</returns>
|
||||||
|
public static unsafe ImVectorWrapper<ushort> IndexLookupWrapped(this ImFontPtr obj) =>
|
||||||
|
obj.NativePtr is null
|
||||||
|
? throw new NullReferenceException()
|
||||||
|
: new(&obj.NativePtr->IndexLookup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrapper for ImVector.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Contained type.</typeparam>
|
||||||
|
public unsafe struct ImVectorWrapper<T> : IList<T>, IList, IReadOnlyList<T>, IDisposable
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
private ImVector* vector;
|
||||||
|
private ImGuiNativeDestroyDelegate? destroyer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImVectorWrapper{T}"/> struct.<br />
|
||||||
|
/// If <paramref name="ownership"/> is set to true, you must call <see cref="Dispose"/> after use,
|
||||||
|
/// and the underlying memory for <see cref="ImVector"/> must have been allocated using
|
||||||
|
/// <see cref="ImGuiNative.igMemAlloc"/>. Otherwise, it will crash.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vector">The underlying vector.</param>
|
||||||
|
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||||
|
/// <param name="ownership">Whether this wrapper owns the vector.</param>
|
||||||
|
public ImVectorWrapper(
|
||||||
|
[NotNull] ImVector* vector,
|
||||||
|
ImGuiNativeDestroyDelegate? destroyer = null,
|
||||||
|
bool ownership = false)
|
||||||
|
{
|
||||||
|
if (vector is null)
|
||||||
|
throw new ArgumentException($"{nameof(vector)} cannot be null.", nameof(this.vector));
|
||||||
|
|
||||||
|
this.vector = vector;
|
||||||
|
this.destroyer = destroyer;
|
||||||
|
this.HasOwnership = ownership;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ImVectorWrapper{T}"/> struct.<br />
|
||||||
|
/// You must call <see cref="Dispose"/> after use.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialCapacity">The initial capacity.</param>
|
||||||
|
/// <param name="destroyer">The destroyer function to call on item removal.</param>
|
||||||
|
public ImVectorWrapper(int initialCapacity = 0, ImGuiNativeDestroyDelegate? destroyer = null)
|
||||||
|
{
|
||||||
|
if (initialCapacity < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(initialCapacity),
|
||||||
|
initialCapacity,
|
||||||
|
$"{nameof(initialCapacity)} cannot be a negative number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vector = (ImVector*)ImGuiNative.igMemAlloc((uint)sizeof(ImVector));
|
||||||
|
if (this.vector is null)
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
*this.vector = default;
|
||||||
|
this.HasOwnership = true;
|
||||||
|
this.destroyer = destroyer;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.EnsureCapacity(initialCapacity);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ImGuiNative.igMemFree(this.vector);
|
||||||
|
this.vector = null;
|
||||||
|
this.HasOwnership = false;
|
||||||
|
this.destroyer = null;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Destroy callback for items.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="self">Pointer to self.</param>
|
||||||
|
public delegate void ImGuiNativeDestroyDelegate(T* self);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the raw vector.
|
||||||
|
/// </summary>
|
||||||
|
public ImVector* RawVector => this.vector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Span{T}"/> view of the underlying ImVector{T}, for the range of <see cref="Length"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Span<T> DataSpan => new(this.DataUnsafe, this.LengthUnsafe);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a <see cref="Span{T}"/> view of the underlying ImVector{T}, for the range of <see cref="Capacity"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Span<T> StorageSpan => new(this.DataUnsafe, this.CapacityUnsafe);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this <see cref="ImVectorWrapper{T}"/> is disposed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDisposed => this.vector is null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this <see cref="ImVectorWrapper{T}"/> has the ownership of the underlying
|
||||||
|
/// <see cref="ImVector"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasOwnership { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the underlying <see cref="ImVector"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ImVector* Vector =>
|
||||||
|
this.vector is null ? throw new ObjectDisposedException(nameof(ImVectorWrapper<T>)) : this.vector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items contained inside the underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
public int Length => this.LengthUnsafe;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of items <b>that can be</b> contained inside the underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
public int Capacity => this.CapacityUnsafe;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pointer to the first item in the data inside underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
public T* Data => this.DataUnsafe;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reference to the number of items contained inside the underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
public ref int LengthUnsafe => ref *&this.Vector->Size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reference to the number of items <b>that can be</b> contained inside the underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
public ref int CapacityUnsafe => ref *&this.Vector->Capacity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the reference to the pointer to the first item in the data inside underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This may be null, if <see cref="Capacity"/> is zero.</remarks>
|
||||||
|
public ref T* DataUnsafe => ref *(T**)&this.Vector->Data;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int ICollection.Count => this.LengthUnsafe;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object ICollection.SyncRoot { get; } = new();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int ICollection<T>.Count => this.LengthUnsafe;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IReadOnlyCollection<T>.Count => this.LengthUnsafe;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool IList.IsFixedSize => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element at the specified index as a reference.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the item.</param>
|
||||||
|
/// <exception cref="IndexOutOfRangeException">If <paramref name="index"/> is out of range.</exception>
|
||||||
|
public ref T this[int index] => ref this.DataUnsafe[this.EnsureIndex(index)];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
T IReadOnlyList<T>.this[int index] => this[index];
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = value is null ? default : (T)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
T IList<T>.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (this.HasOwnership)
|
||||||
|
{
|
||||||
|
this.Clear();
|
||||||
|
this.SetCapacity(0);
|
||||||
|
Debug.Assert(this.vector->Data == 0, "SetCapacity(0) did not free the data");
|
||||||
|
ImGuiNative.igMemFree(this.vector);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vector = null;
|
||||||
|
this.HasOwnership = false;
|
||||||
|
this.destroyer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
|
||||||
|
yield return this[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.Add"/>
|
||||||
|
public void Add(in T item)
|
||||||
|
{
|
||||||
|
this.EnsureCapacityExponential(this.LengthUnsafe + 1);
|
||||||
|
this.DataUnsafe[this.LengthUnsafe++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="List{T}.AddRange"/>
|
||||||
|
public void AddRange(IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
if (items is ICollection { Count: var count })
|
||||||
|
this.EnsureCapacityExponential(this.LengthUnsafe + count);
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
this.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="List{T}.AddRange"/>
|
||||||
|
public void AddRange(Span<T> items)
|
||||||
|
{
|
||||||
|
this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
|
||||||
|
foreach (var item in items)
|
||||||
|
this.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.Clear"/>
|
||||||
|
public void Clear() => this.Clear(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears this vector, optionally skipping destroyer invocation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skipDestroyer">Whether to skip destroyer invocation.</param>
|
||||||
|
public void Clear(bool skipDestroyer)
|
||||||
|
{
|
||||||
|
if (this.destroyer != null && !skipDestroyer)
|
||||||
|
{
|
||||||
|
foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
|
||||||
|
this.destroyer(&this.DataUnsafe[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LengthUnsafe = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.Contains"/>
|
||||||
|
public bool Contains(in T item) => this.IndexOf(in item) != -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size down the underlying ImVector{T}.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reservation">Capacity to reserve.</param>
|
||||||
|
/// <returns>Whether the capacity has been changed.</returns>
|
||||||
|
public bool Compact(int reservation) => this.SetCapacity(Math.Max(reservation, this.LengthUnsafe));
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.CopyTo"/>
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
if (arrayIndex < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(arrayIndex),
|
||||||
|
arrayIndex,
|
||||||
|
$"{nameof(arrayIndex)} is less than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array.Length - arrayIndex < this.LengthUnsafe)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"The number of elements in the source ImVectorWrapper<T> is greater than the available space from arrayIndex to the end of the destination array.",
|
||||||
|
nameof(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (void* p = array)
|
||||||
|
Buffer.MemoryCopy(this.DataUnsafe, p, this.LengthUnsafe * sizeof(T), this.LengthUnsafe * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the capacity of this list is at least the specified <paramref name="capacity"/>.<br />
|
||||||
|
/// On growth, the new capacity exactly matches <paramref name="capacity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The minimum capacity to ensure.</param>
|
||||||
|
/// <returns>Whether the capacity has been changed.</returns>
|
||||||
|
public bool EnsureCapacity(int capacity) => this.CapacityUnsafe < capacity && this.SetCapacity(capacity);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that the capacity of this list is at least the specified <paramref name="capacity"/>.<br />
|
||||||
|
/// On growth, the new capacity may exceed <paramref name="capacity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The minimum capacity to ensure.</param>
|
||||||
|
/// <returns>Whether the capacity has been changed.</returns>
|
||||||
|
public bool EnsureCapacityExponential(int capacity)
|
||||||
|
=> this.EnsureCapacity(1 << ((sizeof(int) * 8) - BitOperations.LeadingZeroCount((uint)this.LengthUnsafe)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resizes the underlying array and fills with zeroes if grown.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">New size.</param>
|
||||||
|
/// <param name="defaultValue">New default value.</param>
|
||||||
|
/// <param name="skipDestroyer">Whether to skip calling destroyer function.</param>
|
||||||
|
public void Resize(int size, in T defaultValue = default, bool skipDestroyer = false)
|
||||||
|
{
|
||||||
|
this.EnsureCapacity(size);
|
||||||
|
var old = this.LengthUnsafe;
|
||||||
|
if (old > size && !skipDestroyer && this.destroyer is not null)
|
||||||
|
{
|
||||||
|
foreach (var v in this.DataSpan[size..])
|
||||||
|
this.destroyer(&v);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.LengthUnsafe = size;
|
||||||
|
if (old < size)
|
||||||
|
this.DataSpan[old..].Fill(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.Remove"/>
|
||||||
|
public bool Remove(in T item)
|
||||||
|
{
|
||||||
|
var index = this.IndexOf(item);
|
||||||
|
if (index == -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.RemoveAt(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IList{T}.IndexOf"/>
|
||||||
|
public int IndexOf(in T item)
|
||||||
|
{
|
||||||
|
foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
|
||||||
|
{
|
||||||
|
if (Equals(item, this.DataUnsafe[i]))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IList{T}.Insert"/>
|
||||||
|
public void Insert(int index, in T item)
|
||||||
|
{
|
||||||
|
// Note: index == this.LengthUnsafe is okay; we're just adding to the end then
|
||||||
|
if (index < 0 || index > this.LengthUnsafe)
|
||||||
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
this.EnsureCapacityExponential(this.CapacityUnsafe + 1);
|
||||||
|
var num = this.LengthUnsafe - index;
|
||||||
|
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + 1, num * sizeof(T), num * sizeof(T));
|
||||||
|
this.DataUnsafe[index] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="List{T}.InsertRange"/>
|
||||||
|
public void InsertRange(int index, IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
if (items is ICollection { Count: var count })
|
||||||
|
{
|
||||||
|
this.EnsureCapacityExponential(this.LengthUnsafe + count);
|
||||||
|
var num = this.LengthUnsafe - index;
|
||||||
|
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + count, num * sizeof(T), num * sizeof(T));
|
||||||
|
foreach (var item in items)
|
||||||
|
this.DataUnsafe[index++] = item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
this.Insert(index++, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="List{T}.AddRange"/>
|
||||||
|
public void InsertRange(int index, Span<T> items)
|
||||||
|
{
|
||||||
|
this.EnsureCapacityExponential(this.LengthUnsafe + items.Length);
|
||||||
|
var num = this.LengthUnsafe - index;
|
||||||
|
Buffer.MemoryCopy(this.DataUnsafe + index, this.DataUnsafe + index + items.Length, num * sizeof(T), num * sizeof(T));
|
||||||
|
foreach (var item in items)
|
||||||
|
this.DataUnsafe[index++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the element at the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index.</param>
|
||||||
|
/// <param name="skipDestroyer">Whether to skip calling the destroyer function.</param>
|
||||||
|
public void RemoveAt(int index, bool skipDestroyer = false)
|
||||||
|
{
|
||||||
|
this.EnsureIndex(index);
|
||||||
|
var num = this.LengthUnsafe - index - 1;
|
||||||
|
if (!skipDestroyer)
|
||||||
|
this.destroyer?.Invoke(&this.DataUnsafe[index]);
|
||||||
|
|
||||||
|
Buffer.MemoryCopy(this.DataUnsafe + index + 1, this.DataUnsafe + index, num * sizeof(T), num * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList<T>.RemoveAt(int index) => this.RemoveAt(index);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.RemoveAt(int index) => this.RemoveAt(index);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the capacity exactly as requested.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">New capacity.</param>
|
||||||
|
/// <returns>Whether the capacity has been changed.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">If <paramref name="capacity"/> is less than <see cref="Length"/>.</exception>
|
||||||
|
/// <exception cref="OutOfMemoryException">If memory for the requested capacity cannot be allocated.</exception>
|
||||||
|
public bool SetCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity < this.LengthUnsafe)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, null);
|
||||||
|
|
||||||
|
if (capacity == this.LengthUnsafe)
|
||||||
|
{
|
||||||
|
if (capacity == 0 && this.DataUnsafe is not null)
|
||||||
|
{
|
||||||
|
ImGuiNative.igMemFree(this.DataUnsafe);
|
||||||
|
this.DataUnsafe = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldAlloc = this.DataUnsafe;
|
||||||
|
var oldSpan = new Span<T>(oldAlloc, this.CapacityUnsafe);
|
||||||
|
|
||||||
|
var newAlloc = (T*)(capacity == 0
|
||||||
|
? null
|
||||||
|
: ImGuiNative.igMemAlloc(checked((uint)(capacity * sizeof(T)))));
|
||||||
|
|
||||||
|
if (newAlloc is null && capacity > 0)
|
||||||
|
throw new OutOfMemoryException();
|
||||||
|
|
||||||
|
var newSpan = new Span<T>(newAlloc, capacity);
|
||||||
|
|
||||||
|
if (!oldSpan.IsEmpty && !newSpan.IsEmpty)
|
||||||
|
oldSpan[..this.LengthUnsafe].CopyTo(newSpan);
|
||||||
|
// #if DEBUG
|
||||||
|
// new Span<byte>(newAlloc + this.LengthUnsafe, sizeof(T) * (capacity - this.LengthUnsafe)).Fill(0xCC);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
if (oldAlloc != null)
|
||||||
|
ImGuiNative.igMemFree(oldAlloc);
|
||||||
|
|
||||||
|
this.DataUnsafe = newAlloc;
|
||||||
|
this.CapacityUnsafe = capacity;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void ICollection<T>.Add(T item) => this.Add(in item);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection<T>.Contains(T item) => this.Contains(in item);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(index),
|
||||||
|
index,
|
||||||
|
$"{nameof(index)} is less than 0.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array.Length - index < this.LengthUnsafe)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"The number of elements in the source ImVectorWrapper<T> is greater than the available space from arrayIndex to the end of the destination array.",
|
||||||
|
nameof(array));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var i in Enumerable.Range(0, this.LengthUnsafe))
|
||||||
|
array.SetValue(this.DataUnsafe[i], index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection<T>.Remove(T item) => this.Remove(in item);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList.Add(object? value)
|
||||||
|
{
|
||||||
|
this.Add(value is null ? default : (T)value);
|
||||||
|
return this.LengthUnsafe - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool IList.Contains(object? value) => this.Contains(value is null ? default : (T)value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList.IndexOf(object? value) => this.IndexOf(value is null ? default : (T)value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Insert(int index, object? value) => this.Insert(index, value is null ? default : (T)value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Remove(object? value) => this.Remove(value is null ? default : (T)value);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList<T>.IndexOf(T item) => this.IndexOf(in item);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList<T>.Insert(int index, T item) => this.Insert(index, in item);
|
||||||
|
|
||||||
|
private int EnsureIndex(int i) => i >= 0 && i < this.LengthUnsafe ? i : throw new IndexOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -16,7 +15,7 @@ namespace Dalamud.IoC.Internal;
|
||||||
/// This is only used to resolve dependencies for plugins.
|
/// This is only used to resolve dependencies for plugins.
|
||||||
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
|
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
internal class ServiceContainer : IServiceProvider, IServiceType
|
internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("SERVICECONTAINER");
|
private static readonly ModuleLog Log = new("SERVICECONTAINER");
|
||||||
|
|
@ -228,7 +227,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||||
serviceType = implementingType;
|
serviceType = implementingType;
|
||||||
|
|
||||||
if (serviceType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
if (serviceType.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null)
|
||||||
{
|
{
|
||||||
if (scope == null)
|
if (scope == null)
|
||||||
{
|
{
|
||||||
|
|
@ -299,7 +298,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
var contains = types.Any(x => x.IsAssignableTo(type));
|
var contains = types.Any(x => x.IsAssignableTo(type));
|
||||||
|
|
||||||
// Scoped services are created on-demand
|
// Scoped services are created on-demand
|
||||||
return contains || type.GetCustomAttribute<ServiceManager.ScopedService>() != null;
|
return contains || type.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parameters = ctor.GetParameters();
|
var parameters = ctor.GetParameters();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Serilog.Core;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
namespace Dalamud.Logging.Internal;
|
namespace Dalamud.Logging.Internal;
|
||||||
|
|
@ -33,6 +32,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Verbose(string messageTemplate, params object[] values)
|
public void Verbose(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -42,6 +42,7 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Verbose(Exception exception, string messageTemplate, params object[] values)
|
public void Verbose(Exception exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Verbose, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Debug(string messageTemplate, params object[] values)
|
public void Debug(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -59,6 +61,7 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Debug(Exception exception, string messageTemplate, params object[] values)
|
public void Debug(Exception exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Debug, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
|
@ -67,6 +70,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Information(string messageTemplate, params object[] values)
|
public void Information(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Information, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Information, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -76,6 +80,7 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Information(Exception exception, string messageTemplate, params object[] values)
|
public void Information(Exception exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Information, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Information, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
|
@ -84,6 +89,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Warning(string messageTemplate, params object[] values)
|
public void Warning(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -93,6 +99,7 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Warning(Exception exception, string messageTemplate, params object[] values)
|
public void Warning(Exception exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Warning, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
|
@ -101,6 +108,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Error(string messageTemplate, params object[] values)
|
public void Error(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Error, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Error, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -110,6 +118,7 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Error(Exception? exception, string messageTemplate, params object[] values)
|
public void Error(Exception? exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Error, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Error, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
|
@ -118,6 +127,7 @@ public class ModuleLog
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Fatal(string messageTemplate, params object[] values)
|
public void Fatal(string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, null, values);
|
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, null, values);
|
||||||
|
|
||||||
|
|
@ -127,9 +137,11 @@ public class ModuleLog
|
||||||
/// <param name="exception">The exception that caused the error.</param>
|
/// <param name="exception">The exception that caused the error.</param>
|
||||||
/// <param name="messageTemplate">The message template.</param>
|
/// <param name="messageTemplate">The message template.</param>
|
||||||
/// <param name="values">Values to log.</param>
|
/// <param name="values">Values to log.</param>
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
public void Fatal(Exception exception, string messageTemplate, params object[] values)
|
public void Fatal(Exception exception, string messageTemplate, params object[] values)
|
||||||
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, values);
|
=> this.WriteLog(LogEventLevel.Fatal, messageTemplate, exception, values);
|
||||||
|
|
||||||
|
[MessageTemplateFormatMethod("messageTemplate")]
|
||||||
private void WriteLog(
|
private void WriteLog(
|
||||||
LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values)
|
LogEventLevel level, string messageTemplate, Exception? exception = null, params object[] values)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@ internal static partial class NativeFunctions
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MB_* from winuser.
|
/// MB_* from winuser.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
public enum MessageBoxType : uint
|
public enum MessageBoxType : uint
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
namespace Dalamud.Networking.Http;
|
namespace Dalamud.Networking.Http;
|
||||||
|
|
||||||
|
|
@ -25,7 +28,16 @@ internal class HappyHttpClient : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
AutomaticDecompression = DecompressionMethods.All,
|
AutomaticDecompression = DecompressionMethods.All,
|
||||||
ConnectCallback = this.SharedHappyEyeballsCallback.ConnectCallback,
|
ConnectCallback = this.SharedHappyEyeballsCallback.ConnectCallback,
|
||||||
});
|
})
|
||||||
|
{
|
||||||
|
DefaultRequestHeaders =
|
||||||
|
{
|
||||||
|
UserAgent =
|
||||||
|
{
|
||||||
|
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -38,7 +39,7 @@ namespace Dalamud.Plugin.Internal;
|
||||||
/// Class responsible for loading and unloading plugins.
|
/// Class responsible for loading and unloading plugins.
|
||||||
/// NOTE: ALL plugin exposed services are marked as dependencies for PluginManager in Service{T}.
|
/// NOTE: ALL plugin exposed services are marked as dependencies for PluginManager in Service{T}.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
#pragma warning disable SA1015
|
#pragma warning disable SA1015
|
||||||
|
|
||||||
// DalamudTextureWrap registers textures to dispose with IM
|
// DalamudTextureWrap registers textures to dispose with IM
|
||||||
|
|
@ -83,6 +84,9 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
||||||
|
|
||||||
|
[ServiceManager.ServiceDependency]
|
||||||
|
private readonly ChatGui chatGui = Service<ChatGui>.Get();
|
||||||
|
|
||||||
static PluginManager()
|
static PluginManager()
|
||||||
{
|
{
|
||||||
DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major;
|
DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major;
|
||||||
|
|
@ -129,12 +133,13 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
|
throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.openInstallerWindowPluginChangelogsLink = Service<ChatGui>.Get().AddChatLinkHandler("Dalamud", 1003, (_, _) =>
|
this.openInstallerWindowPluginChangelogsLink = this.chatGui.AddChatLinkHandler("Dalamud", 1003, (_, _) =>
|
||||||
{
|
{
|
||||||
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
|
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.configuration.PluginTestingOptIns ??= new List<PluginTestingOptIn>();
|
this.configuration.PluginTestingOptIns ??= new();
|
||||||
|
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
|
||||||
|
|
||||||
// NET8 CHORE
|
// NET8 CHORE
|
||||||
//this.ApplyPatches();
|
//this.ApplyPatches();
|
||||||
|
|
@ -198,6 +203,11 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the main repository.
|
||||||
|
/// </summary>
|
||||||
|
public PluginRepository MainRepo { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of all plugin repositories. The main repo should always be first.
|
/// Gets a list of all plugin repositories. The main repo should always be first.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -283,11 +293,9 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
/// <param name="header">The header text to send to chat prior to any update info.</param>
|
/// <param name="header">The header text to send to chat prior to any update info.</param>
|
||||||
public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header)
|
public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header)
|
||||||
{
|
{
|
||||||
var chatGui = Service<ChatGui>.Get();
|
|
||||||
|
|
||||||
if (updateMetadata is { Count: > 0 })
|
if (updateMetadata is { Count: > 0 })
|
||||||
{
|
{
|
||||||
chatGui.Print(new XivChatEntry
|
this.chatGui.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = new SeString(new List<Payload>()
|
Message = new SeString(new List<Payload>()
|
||||||
{
|
{
|
||||||
|
|
@ -306,11 +314,11 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
{
|
{
|
||||||
if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
|
if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
|
||||||
{
|
{
|
||||||
chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
|
this.chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
chatGui.Print(new XivChatEntry
|
this.chatGui.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version, PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)),
|
Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version, PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)),
|
||||||
Type = XivChatType.Urgent,
|
Type = XivChatType.Urgent,
|
||||||
|
|
@ -407,10 +415,10 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
public async Task SetPluginReposFromConfigAsync(bool notify)
|
public async Task SetPluginReposFromConfigAsync(bool notify)
|
||||||
{
|
{
|
||||||
var repos = new List<PluginRepository>() { PluginRepository.MainRepo };
|
var repos = new List<PluginRepository> { this.MainRepo };
|
||||||
repos.AddRange(this.configuration.ThirdRepoList
|
repos.AddRange(this.configuration.ThirdRepoList
|
||||||
.Where(repo => repo.IsEnabled)
|
.Where(repo => repo.IsEnabled)
|
||||||
.Select(repo => new PluginRepository(repo.Url, repo.IsEnabled)));
|
.Select(repo => new PluginRepository(this.happyHttpClient, repo.Url, repo.IsEnabled)));
|
||||||
|
|
||||||
this.Repos = repos;
|
this.Repos = repos;
|
||||||
await this.ReloadPluginMastersAsync(notify);
|
await this.ReloadPluginMastersAsync(notify);
|
||||||
|
|
@ -1199,7 +1207,17 @@ internal partial class PluginManager : IDisposable, IServiceType
|
||||||
private async Task<Stream> DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting)
|
private async Task<Stream> DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting)
|
||||||
{
|
{
|
||||||
var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall;
|
var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall;
|
||||||
var response = await this.happyHttpClient.SharedHttpClient.GetAsync(downloadUrl);
|
var request = new HttpRequestMessage(HttpMethod.Get, downloadUrl)
|
||||||
|
{
|
||||||
|
Headers =
|
||||||
|
{
|
||||||
|
Accept =
|
||||||
|
{
|
||||||
|
new MediaTypeWithQualityHeaderValue("application/zip"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var response = await this.happyHttpClient.SharedHttpClient.SendAsync(request);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
return await response.Content.ReadAsStreamAsync();
|
return await response.Content.ReadAsStreamAsync();
|
||||||
|
|
|
||||||
|
|
@ -267,10 +267,6 @@ internal class LocalPlugin : IDisposable
|
||||||
var pluginManager = await Service<PluginManager>.GetAsync();
|
var pluginManager = await Service<PluginManager>.GetAsync();
|
||||||
var dalamud = await Service<Dalamud>.GetAsync();
|
var dalamud = await Service<Dalamud>.GetAsync();
|
||||||
|
|
||||||
// UiBuilder constructor requires the following two.
|
|
||||||
await Service<InterfaceManager>.GetAsync();
|
|
||||||
await Service<GameFontManager>.GetAsync();
|
|
||||||
|
|
||||||
if (this.manifest.LoadRequiredState == 0)
|
if (this.manifest.LoadRequiredState == 0)
|
||||||
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Networking.Http;
|
using Dalamud.Networking.Http;
|
||||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Dalamud.Plugin.Internal.Types;
|
namespace Dalamud.Plugin.Internal.Types;
|
||||||
|
|
@ -26,49 +28,47 @@ internal class PluginRepository
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string MainRepoUrl = "https://kamori.goats.dev/Plugin/PluginMaster";
|
public const string MainRepoUrl = "https://kamori.goats.dev/Plugin/PluginMaster";
|
||||||
|
|
||||||
private static readonly ModuleLog Log = new("PLUGINR");
|
private const int HttpRequestTimeoutSeconds = 20;
|
||||||
|
|
||||||
private static readonly HttpClient HttpClient = new(new SocketsHttpHandler
|
private static readonly ModuleLog Log = new("PLUGINR");
|
||||||
{
|
private readonly HttpClient httpClient;
|
||||||
AutomaticDecompression = DecompressionMethods.All,
|
|
||||||
ConnectCallback = Service<HappyHttpClient>.Get().SharedHappyEyeballsCallback.ConnectCallback,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
Timeout = TimeSpan.FromSeconds(20),
|
|
||||||
DefaultRequestHeaders =
|
|
||||||
{
|
|
||||||
Accept =
|
|
||||||
{
|
|
||||||
new MediaTypeWithQualityHeaderValue("application/json"),
|
|
||||||
},
|
|
||||||
CacheControl = new CacheControlHeaderValue
|
|
||||||
{
|
|
||||||
NoCache = true,
|
|
||||||
},
|
|
||||||
UserAgent =
|
|
||||||
{
|
|
||||||
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="PluginRepository"/> class.
|
/// Initializes a new instance of the <see cref="PluginRepository"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
|
||||||
/// <param name="pluginMasterUrl">The plugin master URL.</param>
|
/// <param name="pluginMasterUrl">The plugin master URL.</param>
|
||||||
/// <param name="isEnabled">Whether the plugin repo is enabled.</param>
|
/// <param name="isEnabled">Whether the plugin repo is enabled.</param>
|
||||||
public PluginRepository(string pluginMasterUrl, bool isEnabled)
|
public PluginRepository(HappyHttpClient happyHttpClient, string pluginMasterUrl, bool isEnabled)
|
||||||
{
|
{
|
||||||
|
this.httpClient = new(new SocketsHttpHandler
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.All,
|
||||||
|
ConnectCallback = happyHttpClient.SharedHappyEyeballsCallback.ConnectCallback,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(20),
|
||||||
|
DefaultRequestHeaders =
|
||||||
|
{
|
||||||
|
Accept =
|
||||||
|
{
|
||||||
|
new MediaTypeWithQualityHeaderValue("application/json"),
|
||||||
|
},
|
||||||
|
CacheControl = new CacheControlHeaderValue
|
||||||
|
{
|
||||||
|
NoCache = true,
|
||||||
|
},
|
||||||
|
UserAgent =
|
||||||
|
{
|
||||||
|
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
this.PluginMasterUrl = pluginMasterUrl;
|
this.PluginMasterUrl = pluginMasterUrl;
|
||||||
this.IsThirdParty = pluginMasterUrl != MainRepoUrl;
|
this.IsThirdParty = pluginMasterUrl != MainRepoUrl;
|
||||||
this.IsEnabled = isEnabled;
|
this.IsEnabled = isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
|
|
||||||
/// </summary>
|
|
||||||
public static PluginRepository MainRepo => new(MainRepoUrl, true);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the pluginmaster.json URL.
|
/// Gets the pluginmaster.json URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -94,6 +94,14 @@ internal class PluginRepository
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public PluginRepositoryState State { get; private set; }
|
public PluginRepositoryState State { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
|
||||||
|
/// <returns>The new instance of main repository.</returns>
|
||||||
|
public static PluginRepository CreateMainRepo(HappyHttpClient happyHttpClient) =>
|
||||||
|
new(happyHttpClient, MainRepoUrl, true);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reload the plugin master asynchronously in a task.
|
/// Reload the plugin master asynchronously in a task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -107,7 +115,8 @@ internal class PluginRepository
|
||||||
{
|
{
|
||||||
Log.Information($"Fetching repo: {this.PluginMasterUrl}");
|
Log.Information($"Fetching repo: {this.PluginMasterUrl}");
|
||||||
|
|
||||||
using var response = await HttpClient.GetAsync(this.PluginMasterUrl);
|
using var response = await this.GetPluginMaster(this.PluginMasterUrl);
|
||||||
|
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
var data = await response.Content.ReadAsStringAsync();
|
var data = await response.Content.ReadAsStringAsync();
|
||||||
|
|
@ -199,4 +208,17 @@ internal class PluginRepository
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<HttpResponseMessage> GetPluginMaster(string url, int timeout = HttpRequestTimeoutSeconds)
|
||||||
|
{
|
||||||
|
var httpClient = Service<HappyHttpClient>.Get().SharedHttpClient;
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||||
|
request.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
|
||||||
|
|
||||||
|
using var requestCts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout));
|
||||||
|
|
||||||
|
return await httpClient.SendAsync(request, requestCts.Token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Dalamud.Plugin.Ipc.Internal;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC.
|
/// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServiceManager.EarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
internal class DataShare : IServiceType
|
internal class DataShare : IServiceType
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, DataCache> caches = new();
|
private readonly Dictionary<string, DataCache> caches = new();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -29,9 +30,17 @@ internal static class ServiceManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ModuleLog Log = new("SVC");
|
public static readonly ModuleLog Log = new("SVC");
|
||||||
|
|
||||||
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
|
#if DEBUG
|
||||||
|
/// <summary>
|
||||||
|
/// Marks which service constructor the current thread's in. For use from <see cref="Service{T}"/> only.
|
||||||
|
/// </summary>
|
||||||
|
internal static readonly ThreadLocal<Type?> CurrentConstructorServiceType = new();
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "CollectionNeverQueried.Local", Justification = "Debugging purposes")]
|
||||||
private static readonly List<Type> LoadedServices = new();
|
private static readonly List<Type> LoadedServices = new();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
|
||||||
|
|
||||||
private static ManualResetEvent unloadResetEvent = new(false);
|
private static ManualResetEvent unloadResetEvent = new(false);
|
||||||
|
|
||||||
|
|
@ -86,21 +95,34 @@ internal static class ServiceManager
|
||||||
/// <param name="scanner">Instance of <see cref="TargetSigScanner"/>.</param>
|
/// <param name="scanner">Instance of <see cref="TargetSigScanner"/>.</param>
|
||||||
public static void InitializeProvidedServices(Dalamud dalamud, ReliableFileStorage fs, DalamudConfiguration configuration, TargetSigScanner scanner)
|
public static void InitializeProvidedServices(Dalamud dalamud, ReliableFileStorage fs, DalamudConfiguration configuration, TargetSigScanner scanner)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
lock (LoadedServices)
|
lock (LoadedServices)
|
||||||
{
|
{
|
||||||
void ProvideService<T>(T service) where T : IServiceType
|
|
||||||
{
|
|
||||||
Debug.Assert(typeof(T).GetServiceKind().HasFlag(ServiceKind.ProvidedService), "Provided service must have Service attribute");
|
|
||||||
Service<T>.Provide(service);
|
|
||||||
LoadedServices.Add(typeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
ProvideService(dalamud);
|
ProvideService(dalamud);
|
||||||
ProvideService(fs);
|
ProvideService(fs);
|
||||||
ProvideService(configuration);
|
ProvideService(configuration);
|
||||||
ProvideService(new ServiceContainer());
|
ProvideService(new ServiceContainer());
|
||||||
ProvideService(scanner);
|
ProvideService(scanner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
void ProvideService<T>(T service) where T : IServiceType
|
||||||
|
{
|
||||||
|
Debug.Assert(typeof(T).GetServiceKind().HasFlag(ServiceKind.ProvidedService), "Provided service must have Service attribute");
|
||||||
|
Service<T>.Provide(service);
|
||||||
|
LoadedServices.Add(typeof(T));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
ProvideService(dalamud);
|
||||||
|
ProvideService(fs);
|
||||||
|
ProvideService(configuration);
|
||||||
|
ProvideService(new ServiceContainer());
|
||||||
|
ProvideService(scanner);
|
||||||
|
return;
|
||||||
|
|
||||||
|
void ProvideService<T>(T service) where T : IServiceType => Service<T>.Provide(service);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -171,7 +193,22 @@ internal static class ServiceManager
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
|
var whenBlockingComplete = Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
|
||||||
|
while (await Task.WhenAny(whenBlockingComplete, Task.Delay(30000)) != whenBlockingComplete)
|
||||||
|
{
|
||||||
|
if (NativeFunctions.MessageBoxW(
|
||||||
|
IntPtr.Zero,
|
||||||
|
"Dalamud is taking a long time to load. Would you like to continue without Dalamud?\n" +
|
||||||
|
"This can be caused by a faulty plugin, or a bug in Dalamud.",
|
||||||
|
"Dalamud",
|
||||||
|
NativeFunctions.MessageBoxType.IconWarning | NativeFunctions.MessageBoxType.YesNo) == 6)
|
||||||
|
{
|
||||||
|
throw new TimeoutException(
|
||||||
|
"Failed to load services in the given time limit, " +
|
||||||
|
"and the user chose to continue without Dalamud.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BlockingServicesLoadedTaskCompletionSource.SetResult();
|
BlockingServicesLoadedTaskCompletionSource.SetResult();
|
||||||
Timings.Event("BlockingServices Initialized");
|
Timings.Event("BlockingServices Initialized");
|
||||||
}
|
}
|
||||||
|
|
@ -215,13 +252,14 @@ internal static class ServiceManager
|
||||||
tasks.Add((Task)typeof(Service<>)
|
tasks.Add((Task)typeof(Service<>)
|
||||||
.MakeGenericType(serviceType)
|
.MakeGenericType(serviceType)
|
||||||
.InvokeMember(
|
.InvokeMember(
|
||||||
"StartLoader",
|
nameof(Service<IServiceType>.StartLoader),
|
||||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null));
|
null));
|
||||||
servicesToLoad.Remove(serviceType);
|
servicesToLoad.Remove(serviceType);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
tasks.Add(tasks.Last().ContinueWith(task =>
|
tasks.Add(tasks.Last().ContinueWith(task =>
|
||||||
{
|
{
|
||||||
if (task.IsFaulted)
|
if (task.IsFaulted)
|
||||||
|
|
@ -231,6 +269,7 @@ internal static class ServiceManager
|
||||||
LoadedServices.Add(serviceType);
|
LoadedServices.Add(serviceType);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tasks.Any())
|
if (!tasks.Any())
|
||||||
|
|
@ -350,10 +389,12 @@ internal static class ServiceManager
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
lock (LoadedServices)
|
lock (LoadedServices)
|
||||||
{
|
{
|
||||||
LoadedServices.Clear();
|
LoadedServices.Clear();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
unloadResetEvent.Set();
|
unloadResetEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
@ -373,7 +414,7 @@ internal static class ServiceManager
|
||||||
/// <returns>The type of service this type is.</returns>
|
/// <returns>The type of service this type is.</returns>
|
||||||
public static ServiceKind GetServiceKind(this Type type)
|
public static ServiceKind GetServiceKind(this Type type)
|
||||||
{
|
{
|
||||||
var attr = type.GetCustomAttribute<Service>(true)?.GetType();
|
var attr = type.GetCustomAttribute<ServiceAttribute>(true)?.GetType();
|
||||||
if (attr == null)
|
if (attr == null)
|
||||||
return ServiceKind.None;
|
return ServiceKind.None;
|
||||||
|
|
||||||
|
|
@ -381,13 +422,13 @@ internal static class ServiceManager
|
||||||
type.IsAssignableTo(typeof(IServiceType)),
|
type.IsAssignableTo(typeof(IServiceType)),
|
||||||
"Service did not inherit from IServiceType");
|
"Service did not inherit from IServiceType");
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService)))
|
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedServiceAttribute)))
|
||||||
return ServiceKind.BlockingEarlyLoadedService;
|
return ServiceKind.BlockingEarlyLoadedService;
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(EarlyLoadedService)))
|
if (attr.IsAssignableTo(typeof(EarlyLoadedServiceAttribute)))
|
||||||
return ServiceKind.EarlyLoadedService;
|
return ServiceKind.EarlyLoadedService;
|
||||||
|
|
||||||
if (attr.IsAssignableTo(typeof(ScopedService)))
|
if (attr.IsAssignableTo(typeof(ScopedServiceAttribute)))
|
||||||
return ServiceKind.ScopedService;
|
return ServiceKind.ScopedService;
|
||||||
|
|
||||||
return ServiceKind.ProvidedService;
|
return ServiceKind.ProvidedService;
|
||||||
|
|
@ -414,16 +455,57 @@ internal static class ServiceManager
|
||||||
/// Indicates that the class is a service.
|
/// Indicates that the class is a service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class Service : Attribute
|
public abstract class ServiceAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kind">The kind of the service.</param>
|
||||||
|
protected ServiceAttribute(ServiceKind kind) => this.Kind = kind;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the kind of the service.
|
||||||
|
/// </summary>
|
||||||
|
public ServiceKind Kind { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the class is a service, that is provided by some other source.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class ProvidedServiceAttribute : ServiceAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ProvidedServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ProvidedServiceAttribute()
|
||||||
|
: base(ServiceKind.ProvidedService)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that the class is a service, and will be instantiated automatically on startup.
|
/// Indicates that the class is a service, and will be instantiated automatically on startup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class EarlyLoadedService : Service
|
public class EarlyLoadedServiceAttribute : ServiceAttribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EarlyLoadedServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public EarlyLoadedServiceAttribute()
|
||||||
|
: this(ServiceKind.EarlyLoadedService)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="EarlyLoadedServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="kind">The service kind.</param>
|
||||||
|
protected EarlyLoadedServiceAttribute(ServiceKind kind)
|
||||||
|
: base(kind)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -431,8 +513,15 @@ internal static class ServiceManager
|
||||||
/// blocking game main thread until it completes.
|
/// blocking game main thread until it completes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class BlockingEarlyLoadedService : EarlyLoadedService
|
public class BlockingEarlyLoadedServiceAttribute : EarlyLoadedServiceAttribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BlockingEarlyLoadedServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public BlockingEarlyLoadedServiceAttribute()
|
||||||
|
: base(ServiceKind.BlockingEarlyLoadedService)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -440,8 +529,15 @@ internal static class ServiceManager
|
||||||
/// service scope, and that it cannot be created outside of a scope.
|
/// service scope, and that it cannot be created outside of a scope.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
public class ScopedService : Service
|
public class ScopedServiceAttribute : ServiceAttribute
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ScopedServiceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ScopedServiceAttribute()
|
||||||
|
: base(ServiceKind.ScopedService)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -20,17 +19,26 @@ namespace Dalamud;
|
||||||
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
|
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
|
||||||
|
[SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "Service container static type")]
|
||||||
internal static class Service<T> where T : IServiceType
|
internal static class Service<T> where T : IServiceType
|
||||||
{
|
{
|
||||||
|
private static readonly ServiceManager.ServiceAttribute ServiceAttribute;
|
||||||
private static TaskCompletionSource<T> instanceTcs = new();
|
private static TaskCompletionSource<T> instanceTcs = new();
|
||||||
|
private static List<Type>? dependencyServices;
|
||||||
|
|
||||||
static Service()
|
static Service()
|
||||||
{
|
{
|
||||||
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
var type = typeof(T);
|
||||||
|
ServiceAttribute =
|
||||||
|
type.GetCustomAttribute<ServiceManager.ServiceAttribute>(true)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"{nameof(T)} is missing {nameof(ServiceManager.ServiceAttribute)} annotations.");
|
||||||
|
|
||||||
|
var exposeToPlugins = type.GetCustomAttribute<PluginInterfaceAttribute>() != null;
|
||||||
if (exposeToPlugins)
|
if (exposeToPlugins)
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", type.Name);
|
||||||
else
|
else
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", type.Name);
|
||||||
|
|
||||||
if (exposeToPlugins)
|
if (exposeToPlugins)
|
||||||
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
|
||||||
|
|
@ -63,8 +71,8 @@ internal static class Service<T> where T : IServiceType
|
||||||
/// <param name="obj">Object to set.</param>
|
/// <param name="obj">Object to set.</param>
|
||||||
public static void Provide(T obj)
|
public static void Provide(T obj)
|
||||||
{
|
{
|
||||||
instanceTcs.SetResult(obj);
|
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
|
||||||
|
instanceTcs.SetResult(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -83,6 +91,21 @@ internal static class Service<T> where T : IServiceType
|
||||||
/// <returns>The object.</returns>
|
/// <returns>The object.</returns>
|
||||||
public static T Get()
|
public static T Get()
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (ServiceAttribute.Kind != ServiceManager.ServiceKind.ProvidedService
|
||||||
|
&& ServiceManager.CurrentConstructorServiceType.Value is { } currentServiceType)
|
||||||
|
{
|
||||||
|
var deps = ServiceHelpers.GetDependencies(currentServiceType);
|
||||||
|
if (!deps.Contains(typeof(T)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Calling {nameof(Service<IServiceType>)}<{typeof(T)}>.{nameof(Get)} which is not one of the" +
|
||||||
|
$" dependency services is forbidden from the service constructor of {currentServiceType}." +
|
||||||
|
$" This has a high chance of introducing hard-to-debug hangs.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!instanceTcs.Task.IsCompleted)
|
if (!instanceTcs.Task.IsCompleted)
|
||||||
instanceTcs.Task.Wait();
|
instanceTcs.Task.Wait();
|
||||||
return instanceTcs.Task.Result;
|
return instanceTcs.Task.Result;
|
||||||
|
|
@ -116,12 +139,16 @@ internal static class Service<T> where T : IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking.
|
/// Gets an enumerable containing <see cref="Service{T}"/>s that are required for this Service to initialize
|
||||||
|
/// without blocking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>List of dependency services.</returns>
|
/// <returns>List of dependency services.</returns>
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public static List<Type> GetDependencyServices()
|
public static List<Type> GetDependencyServices()
|
||||||
{
|
{
|
||||||
|
if (dependencyServices is not null)
|
||||||
|
return dependencyServices;
|
||||||
|
|
||||||
var res = new List<Type>();
|
var res = new List<Type>();
|
||||||
|
|
||||||
ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name);
|
ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name);
|
||||||
|
|
@ -189,19 +216,42 @@ internal static class Service<T> where T : IServiceType
|
||||||
ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name);
|
ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
var deps = res
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
if (typeof(T).GetCustomAttribute<ServiceManager.BlockingEarlyLoadedServiceAttribute>() is not null)
|
||||||
|
{
|
||||||
|
var offenders = deps.Where(
|
||||||
|
x => x.GetCustomAttribute<ServiceManager.ServiceAttribute>(true)!.Kind
|
||||||
|
is not ServiceManager.ServiceKind.BlockingEarlyLoadedService
|
||||||
|
and not ServiceManager.ServiceKind.ProvidedService)
|
||||||
|
.ToArray();
|
||||||
|
if (offenders.Any())
|
||||||
|
{
|
||||||
|
ServiceManager.Log.Error(
|
||||||
|
"{me} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n{offenders}",
|
||||||
|
typeof(T),
|
||||||
|
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
|
||||||
|
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
|
||||||
|
nameof(ServiceManager.ProvidedServiceAttribute),
|
||||||
|
string.Join("\n", offenders.Select(x => $"\t* {x.Name}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencyServices = deps;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UsedImplicitly]
|
/// <summary>
|
||||||
private static Task<T> StartLoader()
|
/// Starts the service loader. Only to be called from <see cref="ServiceManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The loader task.</returns>
|
||||||
|
internal static Task<T> StartLoader()
|
||||||
{
|
{
|
||||||
if (instanceTcs.Task.IsCompleted)
|
if (instanceTcs.Task.IsCompleted)
|
||||||
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
|
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
|
||||||
|
|
||||||
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType();
|
var attr = ServiceAttribute.GetType();
|
||||||
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true)
|
if (attr.IsAssignableTo(typeof(ServiceManager.EarlyLoadedServiceAttribute)) != true)
|
||||||
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
|
||||||
|
|
||||||
return Task.Run(Timings.AttachTimingHandle(async () =>
|
return Task.Run(Timings.AttachTimingHandle(async () =>
|
||||||
|
|
@ -212,6 +262,7 @@ internal static class Service<T> where T : IServiceType
|
||||||
var instance = await ConstructObject();
|
var instance = await ConstructObject();
|
||||||
instanceTcs.SetResult(instance);
|
instanceTcs.SetResult(instance);
|
||||||
|
|
||||||
|
List<Task>? tasks = null;
|
||||||
foreach (var method in typeof(T).GetMethods(
|
foreach (var method in typeof(T).GetMethods(
|
||||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||||
{
|
{
|
||||||
|
|
@ -221,9 +272,24 @@ internal static class Service<T> where T : IServiceType
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
|
||||||
var args = await Task.WhenAll(method.GetParameters().Select(
|
var args = await Task.WhenAll(method.GetParameters().Select(
|
||||||
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||||
method.Invoke(instance, args);
|
try
|
||||||
|
{
|
||||||
|
if (method.Invoke(instance, args) is Task task)
|
||||||
|
{
|
||||||
|
tasks ??= new();
|
||||||
|
tasks.Add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
tasks ??= new();
|
||||||
|
tasks.Add(Task.FromException(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tasks is not null)
|
||||||
|
await Task.WhenAll(tasks);
|
||||||
|
|
||||||
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +369,19 @@ internal static class Service<T> where T : IServiceType
|
||||||
ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
|
||||||
using (Timings.Start($"{typeof(T).Name} Construct"))
|
using (Timings.Start($"{typeof(T).Name} Construct"))
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
ServiceManager.CurrentConstructorServiceType.Value = typeof(Service<T>);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T)ctor.Invoke(args)!;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ServiceManager.CurrentConstructorServiceType.Value = null;
|
||||||
|
}
|
||||||
|
#else
|
||||||
return (T)ctor.Invoke(args)!;
|
return (T)ctor.Invoke(args)!;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,30 +406,43 @@ internal static class Service<T> where T : IServiceType
|
||||||
internal static class ServiceHelpers
|
internal static class ServiceHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a list of dependencies for a service. Only accepts Service<T> types.
|
/// Get a list of dependencies for a service. Only accepts <see cref="Service{T}"/> types.
|
||||||
/// These are returned as Service<T> types.
|
/// These are returned as <see cref="Service{T}"/> types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serviceType">The dependencies for this service.</param>
|
/// <param name="serviceType">The dependencies for this service.</param>
|
||||||
/// <returns>A list of dependencies.</returns>
|
/// <returns>A list of dependencies.</returns>
|
||||||
public static List<Type> GetDependencies(Type serviceType)
|
public static List<Type> GetDependencies(Type serviceType)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (!serviceType.IsGenericType || serviceType.GetGenericTypeDefinition() != typeof(Service<>))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Expected an instance of {nameof(Service<IServiceType>)}<>",
|
||||||
|
nameof(serviceType));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return (List<Type>)serviceType.InvokeMember(
|
return (List<Type>)serviceType.InvokeMember(
|
||||||
"GetDependencyServices",
|
nameof(Service<IServiceType>.GetDependencyServices),
|
||||||
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null) ?? new List<Type>();
|
null) ?? new List<Type>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the Service<T> type for a given service type.
|
/// Get the <see cref="Service{T}"/> type for a given service type.
|
||||||
/// This will throw if the service type is not a valid service.
|
/// This will throw if the service type is not a valid service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type">The type to obtain a Service<T> for.</param>
|
/// <param name="type">The type to obtain a <see cref="Service{T}"/> for.</param>
|
||||||
/// <returns>The Service<T>.</returns>
|
/// <returns>The <see cref="Service{T}"/>.</returns>
|
||||||
public static Type GetAsService(Type type)
|
public static Type GetAsService(Type type)
|
||||||
{
|
{
|
||||||
return typeof(Service<>)
|
#if DEBUG
|
||||||
.MakeGenericType(type);
|
if (!type.IsAssignableTo(typeof(IServiceType)))
|
||||||
|
throw new ArgumentException($"Expected an instance of {nameof(IServiceType)}", nameof(type));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return typeof(Service<>).MakeGenericType(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
Dalamud/Storage/Assets/DalamudAssetAttribute.cs
Normal file
36
Dalamud/Storage/Assets/DalamudAssetAttribute.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores the basic information of a Dalamud asset.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
internal class DalamudAssetAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudAssetAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="purpose">The purpose.</param>
|
||||||
|
/// <param name="data">The data.</param>
|
||||||
|
/// <param name="required">Whether the asset is required.</param>
|
||||||
|
public DalamudAssetAttribute(DalamudAssetPurpose purpose, byte[]? data = null, bool required = true)
|
||||||
|
{
|
||||||
|
this.Purpose = purpose;
|
||||||
|
this.Data = data;
|
||||||
|
this.Required = required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the purpose of the asset.
|
||||||
|
/// </summary>
|
||||||
|
public DalamudAssetPurpose Purpose { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data, if available.
|
||||||
|
/// </summary>
|
||||||
|
public byte[]? Data { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the asset is required.
|
||||||
|
/// </summary>
|
||||||
|
public bool Required { get; }
|
||||||
|
}
|
||||||
17
Dalamud/Storage/Assets/DalamudAssetExtensions.cs
Normal file
17
Dalamud/Storage/Assets/DalamudAssetExtensions.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using Dalamud.Utility;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="DalamudAsset"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class DalamudAssetExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the purpose.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The asset.</param>
|
||||||
|
/// <returns>The purpose.</returns>
|
||||||
|
public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) =>
|
||||||
|
asset.GetAttribute<DalamudAssetAttribute>()?.Purpose ?? DalamudAssetPurpose.Empty;
|
||||||
|
}
|
||||||
366
Dalamud/Storage/Assets/DalamudAssetManager.cs
Normal file
366
Dalamud/Storage/Assets/DalamudAssetManager.cs
Normal file
|
|
@ -0,0 +1,366 @@
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
using Dalamud.IoC;
|
||||||
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Networking.Http;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using Dalamud.Utility.Timing;
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A concrete class for <see cref="IDalamudAssetManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
[PluginInterface]
|
||||||
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
|
#pragma warning disable SA1015
|
||||||
|
[ResolveVia<IDalamudAssetManager>]
|
||||||
|
#pragma warning restore SA1015
|
||||||
|
internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudAssetManager
|
||||||
|
{
|
||||||
|
private const int DownloadAttemptCount = 10;
|
||||||
|
private const int RenameAttemptCount = 10;
|
||||||
|
|
||||||
|
private readonly object syncRoot = new();
|
||||||
|
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
||||||
|
private readonly Dictionary<DalamudAsset, Task<FileStream>?> fileStreams;
|
||||||
|
private readonly Dictionary<DalamudAsset, Task<IDalamudTextureWrap>?> textureWraps;
|
||||||
|
private readonly Dalamud dalamud;
|
||||||
|
private readonly HappyHttpClient httpClient;
|
||||||
|
private readonly string localSourceDirectory;
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource;
|
||||||
|
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
|
[ServiceManager.ServiceConstructor]
|
||||||
|
private DalamudAssetManager(Dalamud dalamud, HappyHttpClient httpClient)
|
||||||
|
{
|
||||||
|
this.dalamud = dalamud;
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.localSourceDirectory = Path.Combine(this.dalamud.AssetDirectory.FullName, "..", "local");
|
||||||
|
Directory.CreateDirectory(this.localSourceDirectory);
|
||||||
|
this.scopedFinalizer.Add(this.cancellationTokenSource = new());
|
||||||
|
|
||||||
|
this.fileStreams = Enum.GetValues<DalamudAsset>().ToDictionary(x => x, _ => (Task<FileStream>?)null);
|
||||||
|
this.textureWraps = Enum.GetValues<DalamudAsset>().ToDictionary(x => x, _ => (Task<IDalamudTextureWrap>?)null);
|
||||||
|
|
||||||
|
var loadTimings = Timings.Start("DAM LoadAll");
|
||||||
|
this.WaitForAllRequiredAssets().ContinueWith(_ => loadTimings.Dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cancellationTokenSource.Cancel();
|
||||||
|
Task.WaitAll(
|
||||||
|
Array.Empty<Task>()
|
||||||
|
.Concat(this.fileStreams.Values)
|
||||||
|
.Concat(this.textureWraps.Values)
|
||||||
|
.Where(x => x is not null)
|
||||||
|
.ToArray());
|
||||||
|
this.scopedFinalizer.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits for all the required assets to be ready. Will result in a faulted task, if any of the required assets
|
||||||
|
/// has failed to load.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The task.</returns>
|
||||||
|
[Pure]
|
||||||
|
public Task WaitForAllRequiredAssets()
|
||||||
|
{
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
return Task.WhenAll(
|
||||||
|
Enum.GetValues<DalamudAsset>()
|
||||||
|
.Where(x => x is not DalamudAsset.Empty4X4)
|
||||||
|
.Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Required is true)
|
||||||
|
.Select(this.CreateStreamAsync)
|
||||||
|
.Select(x => x.ToContentDisposedTask()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
public bool IsStreamImmediatelyAvailable(DalamudAsset asset) =>
|
||||||
|
asset.GetAttribute<DalamudAssetAttribute>()?.Data is not null
|
||||||
|
|| this.fileStreams[asset]?.IsCompletedSuccessfully is true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
public Stream CreateStream(DalamudAsset asset)
|
||||||
|
{
|
||||||
|
var s = this.CreateStreamAsync(asset);
|
||||||
|
s.Wait();
|
||||||
|
if (s.IsCompletedSuccessfully)
|
||||||
|
return s.Result;
|
||||||
|
if (s.Exception is not null)
|
||||||
|
throw new AggregateException(s.Exception.InnerExceptions);
|
||||||
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
public Task<Stream> CreateStreamAsync(DalamudAsset asset)
|
||||||
|
{
|
||||||
|
if (asset.GetAttribute<DalamudAssetAttribute>() is { Data: { } rawData })
|
||||||
|
return Task.FromResult<Stream>(new MemoryStream(rawData, false));
|
||||||
|
|
||||||
|
Task<FileStream> task;
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
throw new ObjectDisposedException(nameof(DalamudAssetManager));
|
||||||
|
|
||||||
|
task = this.fileStreams[asset] ??= CreateInnerAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.TransformImmediate(
|
||||||
|
task,
|
||||||
|
x => (Stream)new FileStream(
|
||||||
|
x.Name,
|
||||||
|
FileMode.Open,
|
||||||
|
FileAccess.Read,
|
||||||
|
FileShare.Read,
|
||||||
|
4096,
|
||||||
|
FileOptions.Asynchronous | FileOptions.SequentialScan));
|
||||||
|
|
||||||
|
async Task<FileStream> CreateInnerAsync()
|
||||||
|
{
|
||||||
|
string path;
|
||||||
|
List<Exception?> exceptions = null;
|
||||||
|
foreach (var name in asset.GetAttributes<DalamudAssetPathAttribute>().Select(x => x.FileName))
|
||||||
|
{
|
||||||
|
if (!File.Exists(path = Path.Combine(this.dalamud.AssetDirectory.FullName, name)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(path = Path.Combine(this.localSourceDirectory, asset.ToString())))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempPath = $"{path}.{Environment.ProcessId:x}.{Environment.CurrentManagedThreadId:x}";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < DownloadAttemptCount; i++)
|
||||||
|
{
|
||||||
|
var attemptedAny = false;
|
||||||
|
foreach (var url in asset.GetAttributes<DalamudAssetOnlineSourceAttribute>())
|
||||||
|
{
|
||||||
|
Log.Information("[{who}] {asset}: Trying {url}", nameof(DalamudAssetManager), asset, url);
|
||||||
|
attemptedAny = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var tempPathStream = File.Open(tempPath, FileMode.Create, FileAccess.Write);
|
||||||
|
await url.DownloadAsync(
|
||||||
|
this.httpClient.SharedHttpClient,
|
||||||
|
tempPathStream,
|
||||||
|
this.cancellationTokenSource.Token);
|
||||||
|
tempPathStream.Dispose();
|
||||||
|
for (var j = RenameAttemptCount; ; j--)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Move(tempPath, path);
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
if (j == 0)
|
||||||
|
throw;
|
||||||
|
Log.Warning(
|
||||||
|
ioe,
|
||||||
|
"[{who}] {asset}: Renaming failed; trying again {n} more times",
|
||||||
|
nameof(DalamudAssetManager),
|
||||||
|
asset,
|
||||||
|
j);
|
||||||
|
await Task.Delay(1000, this.cancellationTokenSource.Token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
Log.Error(e, "[{who}] {asset}: Failed {url}", nameof(DalamudAssetManager), asset, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attemptedAny)
|
||||||
|
throw new FileNotFoundException($"Failed to find the asset {asset}.", asset.ToString());
|
||||||
|
|
||||||
|
// Wait up to 5 minutes
|
||||||
|
var delay = Math.Min(300, (1 << i) * 1000);
|
||||||
|
Log.Error(
|
||||||
|
"[{who}] {asset}: Failed to download. Trying again in {sec} seconds...",
|
||||||
|
nameof(DalamudAssetManager),
|
||||||
|
asset,
|
||||||
|
delay);
|
||||||
|
await Task.Delay(delay * 1000, this.cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException($"Failed to load the asset {asset}.", asset.ToString());
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(e);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(tempPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// don't care
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AggregateException(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
public IDalamudTextureWrap GetDalamudTextureWrap(DalamudAsset asset) =>
|
||||||
|
ExtractResult(this.GetDalamudTextureWrapAsync(asset));
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
[return: NotNullIfNotNull(nameof(defaultWrap))]
|
||||||
|
public IDalamudTextureWrap? GetDalamudTextureWrap(DalamudAsset asset, IDalamudTextureWrap? defaultWrap)
|
||||||
|
{
|
||||||
|
var task = this.GetDalamudTextureWrapAsync(asset);
|
||||||
|
return task.IsCompletedSuccessfully ? task.Result : defaultWrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
[Pure]
|
||||||
|
public Task<IDalamudTextureWrap> GetDalamudTextureWrapAsync(DalamudAsset asset)
|
||||||
|
{
|
||||||
|
var purpose = asset.GetPurpose();
|
||||||
|
if (purpose is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(asset), asset, "The asset cannot be taken as a Texture2D.");
|
||||||
|
|
||||||
|
Task<IDalamudTextureWrap> task;
|
||||||
|
lock (this.syncRoot)
|
||||||
|
{
|
||||||
|
if (this.isDisposed)
|
||||||
|
throw new ObjectDisposedException(nameof(DalamudAssetManager));
|
||||||
|
|
||||||
|
task = this.textureWraps[asset] ??= CreateInnerAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
|
||||||
|
async Task<IDalamudTextureWrap> CreateInnerAsync()
|
||||||
|
{
|
||||||
|
var buf = Array.Empty<byte>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var im = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
|
||||||
|
await using var stream = await this.CreateStreamAsync(asset);
|
||||||
|
var length = checked((int)stream.Length);
|
||||||
|
buf = ArrayPool<byte>.Shared.Rent(length);
|
||||||
|
stream.ReadExactly(buf, 0, length);
|
||||||
|
var image = purpose switch
|
||||||
|
{
|
||||||
|
DalamudAssetPurpose.TextureFromPng => im.LoadImage(buf),
|
||||||
|
DalamudAssetPurpose.TextureFromRaw =>
|
||||||
|
asset.GetAttribute<DalamudAssetRawTextureAttribute>() is { } raw
|
||||||
|
? im.LoadImageFromDxgiFormat(buf, raw.Pitch, raw.Width, raw.Height, raw.Format)
|
||||||
|
: throw new InvalidOperationException(
|
||||||
|
"TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
var disposeDeferred =
|
||||||
|
this.scopedFinalizer.Add(image)
|
||||||
|
?? throw new InvalidOperationException("Something went wrong very badly");
|
||||||
|
return new DisposeSuppressingDalamudTextureWrap(disposeDeferred);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "[{name}] Failed to load {asset}.", nameof(DalamudAssetManager), asset);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T ExtractResult<T>(Task<T> t) => t.IsCompleted ? t.Result : t.GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
private Task<TOut> TransformImmediate<TIn, TOut>(Task<TIn> task, Func<TIn, TOut> transformer)
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
return Task.FromResult(transformer(task.Result));
|
||||||
|
if (task.Exception is { } exc)
|
||||||
|
return Task.FromException<TOut>(exc);
|
||||||
|
return task.ContinueWith(_ => this.TransformImmediate(task, transformer)).Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DisposeSuppressingDalamudTextureWrap : IDalamudTextureWrap
|
||||||
|
{
|
||||||
|
private readonly IDalamudTextureWrap innerWrap;
|
||||||
|
|
||||||
|
public DisposeSuppressingDalamudTextureWrap(IDalamudTextureWrap wrap) => this.innerWrap = wrap;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr ImGuiHandle => this.innerWrap.ImGuiHandle;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Width => this.innerWrap.Width;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Height => this.innerWrap.Height;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// suppressed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Dalamud/Storage/Assets/DalamudAssetOnlineSourceAttribute.cs
Normal file
48
Dalamud/Storage/Assets/DalamudAssetOnlineSourceAttribute.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks that an asset can be download from online.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
internal class DalamudAssetOnlineSourceAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudAssetOnlineSourceAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
public DalamudAssetOnlineSourceAttribute(string url)
|
||||||
|
{
|
||||||
|
this.Url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the source URL of the file.
|
||||||
|
/// </summary>
|
||||||
|
public string Url { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads to the given stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="stream">The stream.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The task.</returns>
|
||||||
|
public async Task DownloadAsync(HttpClient client, Stream stream, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var resp = await client.GetAsync(this.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
if (resp.StatusCode != HttpStatusCode.OK)
|
||||||
|
throw new NotSupportedException($"Only 200 OK is supported; got {resp.StatusCode}");
|
||||||
|
|
||||||
|
await using var readStream = await resp.Content.ReadAsStreamAsync(cancellationToken);
|
||||||
|
await readStream.CopyToAsync(stream, cancellationToken);
|
||||||
|
if (resp.Content.Headers.ContentLength is { } length && stream.Length != length)
|
||||||
|
throw new IOException($"Expected {length} bytes; got {stream.Length} bytes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Dalamud/Storage/Assets/DalamudAssetPathAttribute.cs
Normal file
21
Dalamud/Storage/Assets/DalamudAssetPathAttribute.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// File names to look up in Dalamud assets.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
|
internal class DalamudAssetPathAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudAssetPathAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathComponents">The path components.</param>
|
||||||
|
public DalamudAssetPathAttribute(params string[] pathComponents) => this.FileName = Path.Join(pathComponents);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file name.
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; }
|
||||||
|
}
|
||||||
27
Dalamud/Storage/Assets/DalamudAssetPurpose.cs
Normal file
27
Dalamud/Storage/Assets/DalamudAssetPurpose.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Purposes of a Dalamud asset.
|
||||||
|
/// </summary>
|
||||||
|
public enum DalamudAssetPurpose
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The asset has no purpose.
|
||||||
|
/// </summary>
|
||||||
|
Empty = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The asset is a .png file, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
TextureFromPng = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The asset is a raw texture, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
|
||||||
|
/// </summary>
|
||||||
|
TextureFromRaw = 1001,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The asset is a font file.
|
||||||
|
/// </summary>
|
||||||
|
Font = 2000,
|
||||||
|
}
|
||||||
45
Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
Normal file
45
Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
using SharpDX.DXGI;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provide raw texture data directly.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
internal class DalamudAssetRawTextureAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DalamudAssetRawTextureAttribute"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="width">The width.</param>
|
||||||
|
/// <param name="pitch">The pitch.</param>
|
||||||
|
/// <param name="height">The height.</param>
|
||||||
|
/// <param name="format">The format.</param>
|
||||||
|
public DalamudAssetRawTextureAttribute(int width, int pitch, int height, Format format)
|
||||||
|
{
|
||||||
|
this.Width = width;
|
||||||
|
this.Pitch = pitch;
|
||||||
|
this.Height = height;
|
||||||
|
this.Format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the width.
|
||||||
|
/// </summary>
|
||||||
|
public int Width { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the pitch.
|
||||||
|
/// </summary>
|
||||||
|
public int Pitch { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the height.
|
||||||
|
/// </summary>
|
||||||
|
public int Height { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the format.
|
||||||
|
/// </summary>
|
||||||
|
public Format Format { get; }
|
||||||
|
}
|
||||||
79
Dalamud/Storage/Assets/IDalamudAssetManager.cs
Normal file
79
Dalamud/Storage/Assets/IDalamudAssetManager.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Interface.Internal;
|
||||||
|
|
||||||
|
namespace Dalamud.Storage.Assets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds Dalamud Assets' handles hostage, so that they do not get closed while Dalamud is running.<br />
|
||||||
|
/// Also, attempts to load optional assets.<br />
|
||||||
|
/// <br />
|
||||||
|
/// <strong>Note on <see cref="PureAttribute"/></strong><br />
|
||||||
|
/// It will help you get notified if you discard the result of functions, mostly likely because of a mistake.
|
||||||
|
/// Think of C++ [[nodiscard]]. Also, like the intended meaning of the attribute, such methods will not have
|
||||||
|
/// externally visible state changes.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IDalamudAssetManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the shared texture wrap for <see cref="DalamudAsset.Empty4X4"/>.
|
||||||
|
/// </summary>
|
||||||
|
IDalamudTextureWrap Empty4X4 { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the stream for the asset is instantly available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The asset.</param>
|
||||||
|
/// <returns>Whether the stream of an asset is immediately available.</returns>
|
||||||
|
[Pure]
|
||||||
|
bool IsStreamImmediatelyAvailable(DalamudAsset asset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a stream backed by the specified asset, waiting as necessary.<br />
|
||||||
|
/// <strong>Call <see cref="IDisposable.Dispose"/> after use.</strong>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The asset.</param>
|
||||||
|
/// <returns>The stream.</returns>
|
||||||
|
[Pure]
|
||||||
|
Stream CreateStream(DalamudAsset asset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a stream backed by the specified asset.<br />
|
||||||
|
/// <strong>Call <see cref="IDisposable.Dispose"/> after use.</strong>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The asset.</param>
|
||||||
|
/// <returns>The stream, wrapped inside a <see cref="Stream"/>.</returns>
|
||||||
|
[Pure]
|
||||||
|
Task<Stream> CreateStreamAsync(DalamudAsset asset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/>, after waiting as necessary.<br />
|
||||||
|
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The texture asset.</param>
|
||||||
|
/// <returns>The texture wrap.</returns>
|
||||||
|
[Pure]
|
||||||
|
IDalamudTextureWrap GetDalamudTextureWrap(DalamudAsset asset);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/> if it is available instantly;
|
||||||
|
/// if it is not ready, returns <paramref name="defaultWrap"/>.<br />
|
||||||
|
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The texture asset.</param>
|
||||||
|
/// <param name="defaultWrap">The default return value, if the asset is not ready for whatever reason.</param>
|
||||||
|
/// <returns>The texture wrap.</returns>
|
||||||
|
[Pure]
|
||||||
|
IDalamudTextureWrap? GetDalamudTextureWrap(DalamudAsset asset, IDalamudTextureWrap? defaultWrap);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/> in a <see cref="Task{T}"/>.<br />
|
||||||
|
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="asset">The texture asset.</param>
|
||||||
|
/// <returns>The new texture wrap, wrapped inside a <see cref="Task{T}"/>.</returns>
|
||||||
|
[Pure]
|
||||||
|
Task<IDalamudTextureWrap> GetDalamudTextureWrapAsync(DalamudAsset asset);
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ namespace Dalamud.Storage;
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is not an early-loaded service, as it is needed before they are initialized.
|
/// This is not an early-loaded service, as it is needed before they are initialized.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[ServiceManager.Service]
|
[ServiceManager.ProvidedService]
|
||||||
public class ReliableFileStorage : IServiceType, IDisposable
|
public class ReliableFileStorage : IServiceType, IDisposable
|
||||||
{
|
{
|
||||||
private static readonly ModuleLog Log = new("VFS");
|
private static readonly ModuleLog Log = new("VFS");
|
||||||
|
|
|
||||||
392
Dalamud/Utility/DisposeSafety.cs
Normal file
392
Dalamud/Utility/DisposeSafety.cs
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utilities for disposing stuff.
|
||||||
|
/// </summary>
|
||||||
|
public static class DisposeSafety
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface that marks a disposable that it can call back on dispose.
|
||||||
|
/// </summary>
|
||||||
|
public interface IDisposeCallback : IDisposable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event to be fired before object dispose. First parameter is the object iself.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IDisposeCallback>? BeforeDispose;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event to be fired after object dispose. First parameter is the object iself.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IDisposeCallback, Exception?>? AfterDispose;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a proxy <see cref="IDisposable"/> that on dispose will dispose the result of the given
|
||||||
|
/// <see cref="Task{T}"/>.<br />
|
||||||
|
/// If any exception has occurred, it will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
/// <typeparam name="T">A disposable type.</typeparam>
|
||||||
|
/// <returns>The proxy <see cref="IDisposable"/>.</returns>
|
||||||
|
public static IDisposable ToDisposableIgnoreExceptions<T>(this Task<T> task)
|
||||||
|
where T : IDisposable
|
||||||
|
{
|
||||||
|
return Disposable.Create(() => task.ContinueWith(r =>
|
||||||
|
{
|
||||||
|
_ = r.Exception;
|
||||||
|
if (r.IsCompleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r.Dispose();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms <paramref name="task"/> into a <see cref="Task"/>, disposing the content as necessary.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="task">The task.</param>
|
||||||
|
/// <param name="ignoreAllExceptions">Ignore all exceptions.</param>
|
||||||
|
/// <typeparam name="T">A disposable type.</typeparam>
|
||||||
|
/// <returns>A wrapper for the task.</returns>
|
||||||
|
public static Task ToContentDisposedTask<T>(this Task<T> task, bool ignoreAllExceptions = false)
|
||||||
|
where T : IDisposable => task.ContinueWith(
|
||||||
|
r =>
|
||||||
|
{
|
||||||
|
if (!r.IsCompletedSuccessfully)
|
||||||
|
return ignoreAllExceptions ? Task.CompletedTask : r;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
r.Result.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!ignoreAllExceptions)
|
||||||
|
{
|
||||||
|
return Task.FromException(
|
||||||
|
new AggregateException(
|
||||||
|
new[] { e }.Concat(
|
||||||
|
(IEnumerable<Exception>)r.Exception?.InnerExceptions
|
||||||
|
?? new[] { new OperationCanceledException() })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}).Unwrap();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a proxy <see cref="IDisposable"/> that on dispose will dispose all the elements of the given
|
||||||
|
/// <see cref="IEnumerable{T}"/> of <typeparamref name="T"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposables">The disposables.</param>
|
||||||
|
/// <typeparam name="T">The disposable types.</typeparam>
|
||||||
|
/// <returns>The proxy <see cref="IDisposable"/>.</returns>
|
||||||
|
/// <exception cref="AggregateException">Error.</exception>
|
||||||
|
public static IDisposable AggregateToDisposable<T>(this IEnumerable<T>? disposables)
|
||||||
|
where T : IDisposable
|
||||||
|
{
|
||||||
|
if (disposables is not T[] array)
|
||||||
|
array = disposables?.ToArray() ?? Array.Empty<T>();
|
||||||
|
|
||||||
|
return Disposable.Create(() =>
|
||||||
|
{
|
||||||
|
List<Exception?> exceptions = null;
|
||||||
|
foreach (var d in array)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
d?.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception de)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(de);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exceptions is not null)
|
||||||
|
throw new AggregateException(exceptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Utility class for managing finalizing stuff.
|
||||||
|
/// </summary>
|
||||||
|
public class ScopedFinalizer : IDisposeCallback, IAsyncDisposable
|
||||||
|
{
|
||||||
|
private readonly List<object> objects = new();
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<IDisposeCallback>? BeforeDispose;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event Action<IDisposeCallback, Exception?>? AfterDispose;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.EnsureCapacity"/>
|
||||||
|
public void EnsureCapacity(int capacity) => this.objects.EnsureCapacity(capacity);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.Push"/>
|
||||||
|
/// <returns>The parameter.</returns>
|
||||||
|
[return: NotNullIfNotNull(nameof(d))]
|
||||||
|
public T? Add<T>(T? d) where T : IDisposable
|
||||||
|
{
|
||||||
|
if (d is not null)
|
||||||
|
this.objects.Add(this.CheckAdd(d));
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.Push"/>
|
||||||
|
[return: NotNullIfNotNull(nameof(d))]
|
||||||
|
public Action? Add(Action? d)
|
||||||
|
{
|
||||||
|
if (d is not null)
|
||||||
|
this.objects.Add(this.CheckAdd(d));
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.Push"/>
|
||||||
|
[return: NotNullIfNotNull(nameof(d))]
|
||||||
|
public Func<Task>? Add(Func<Task>? d)
|
||||||
|
{
|
||||||
|
if (d is not null)
|
||||||
|
this.objects.Add(this.CheckAdd(d));
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.Push"/>
|
||||||
|
public GCHandle Add(GCHandle d)
|
||||||
|
{
|
||||||
|
if (d != default)
|
||||||
|
this.objects.Add(this.CheckAdd(d));
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue all the given <see cref="IDisposable"/> to be disposed later.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ds">Disposables.</param>
|
||||||
|
public void AddRange(IEnumerable<IDisposable?> ds) =>
|
||||||
|
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue all the given <see cref="IDisposable"/> to be run later.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ds">Actions.</param>
|
||||||
|
public void AddRange(IEnumerable<Action?> ds) =>
|
||||||
|
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue all the given <see cref="Func{T}"/> returning <see cref="Task"/> to be run later.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ds">Func{Task}s.</param>
|
||||||
|
public void AddRange(IEnumerable<Func<Task>?> ds) =>
|
||||||
|
this.objects.AddRange(ds.Where(d => d is not null).Select(d => (object)this.CheckAdd(d)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue all the given <see cref="GCHandle"/> to be disposed later.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ds">GCHandles.</param>
|
||||||
|
public void AddRange(IEnumerable<GCHandle> ds) =>
|
||||||
|
this.objects.AddRange(ds.Select(d => (object)this.CheckAdd(d)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancel all pending disposals.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Use this after successful initialization of multiple disposables.</remarks>
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
foreach (var o in this.objects)
|
||||||
|
this.CheckRemove(o);
|
||||||
|
this.objects.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Stack{T}.EnsureCapacity"/>
|
||||||
|
/// <returns>This for method chaining.</returns>
|
||||||
|
public ScopedFinalizer WithEnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
this.EnsureCapacity(capacity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Add{T}"/>
|
||||||
|
/// <returns>This for method chaining.</returns>
|
||||||
|
public ScopedFinalizer With(IDisposable d)
|
||||||
|
{
|
||||||
|
this.Add(d);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Add(Action)"/>
|
||||||
|
/// <returns>This for method chaining.</returns>
|
||||||
|
public ScopedFinalizer With(Action d)
|
||||||
|
{
|
||||||
|
this.Add(d);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Add(Func{Task})"/>
|
||||||
|
/// <returns>This for method chaining.</returns>
|
||||||
|
public ScopedFinalizer With(Func<Task> d)
|
||||||
|
{
|
||||||
|
this.Add(d);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="Add(GCHandle)"/>
|
||||||
|
/// <returns>This for method chaining.</returns>
|
||||||
|
public ScopedFinalizer With(GCHandle d)
|
||||||
|
{
|
||||||
|
this.Add(d);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
this.BeforeDispose?.InvokeSafely(this);
|
||||||
|
|
||||||
|
List<Exception>? exceptions = null;
|
||||||
|
while (this.objects.Any())
|
||||||
|
{
|
||||||
|
var obj = this.objects[^1];
|
||||||
|
this.objects.RemoveAt(this.objects.Count - 1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case IDisposable x:
|
||||||
|
x.Dispose();
|
||||||
|
break;
|
||||||
|
case Action a:
|
||||||
|
a.Invoke();
|
||||||
|
break;
|
||||||
|
case Func<Task> a:
|
||||||
|
a.Invoke().Wait();
|
||||||
|
break;
|
||||||
|
case GCHandle a:
|
||||||
|
a.Free();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objects.TrimExcess();
|
||||||
|
|
||||||
|
if (exceptions is not null)
|
||||||
|
{
|
||||||
|
var exs = exceptions.Count == 1 ? exceptions[0] : new AggregateException(exceptions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.AfterDispose?.Invoke(this, exs);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// whatever
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
this.BeforeDispose?.InvokeSafely(this);
|
||||||
|
|
||||||
|
List<Exception>? exceptions = null;
|
||||||
|
while (this.objects.Any())
|
||||||
|
{
|
||||||
|
var obj = this.objects[^1];
|
||||||
|
this.objects.RemoveAt(this.objects.Count - 1);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (obj)
|
||||||
|
{
|
||||||
|
case IAsyncDisposable x:
|
||||||
|
await x.DisposeAsync();
|
||||||
|
break;
|
||||||
|
case IDisposable x:
|
||||||
|
x.Dispose();
|
||||||
|
break;
|
||||||
|
case Func<Task> a:
|
||||||
|
await a.Invoke();
|
||||||
|
break;
|
||||||
|
case Action a:
|
||||||
|
a.Invoke();
|
||||||
|
break;
|
||||||
|
case GCHandle a:
|
||||||
|
a.Free();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exceptions ??= new();
|
||||||
|
exceptions.Add(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objects.TrimExcess();
|
||||||
|
|
||||||
|
if (exceptions is not null)
|
||||||
|
{
|
||||||
|
var exs = exceptions.Count == 1 ? exceptions[0] : new AggregateException(exceptions);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.AfterDispose?.Invoke(this, exs);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// whatever
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private T CheckAdd<T>(T item)
|
||||||
|
{
|
||||||
|
if (item is IDisposeCallback dc)
|
||||||
|
dc.BeforeDispose += this.OnItemDisposed;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckRemove(object item)
|
||||||
|
{
|
||||||
|
if (item is IDisposeCallback dc)
|
||||||
|
dc.BeforeDispose -= this.OnItemDisposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemDisposed(IDisposeCallback obj)
|
||||||
|
{
|
||||||
|
obj.BeforeDispose -= this.OnItemDisposed;
|
||||||
|
this.objects.Remove(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Dalamud.Utility;
|
namespace Dalamud.Utility;
|
||||||
|
|
@ -8,6 +8,26 @@ namespace Dalamud.Utility;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class EnumExtensions
|
public static class EnumExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets attributes on an enum.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TAttribute">The type of attribute to get.</typeparam>
|
||||||
|
/// <param name="value">The enum value that has an attached attribute.</param>
|
||||||
|
/// <returns>The enumerable of the attached attributes.</returns>
|
||||||
|
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Enum value)
|
||||||
|
where TAttribute : Attribute
|
||||||
|
{
|
||||||
|
var type = value.GetType();
|
||||||
|
var name = Enum.GetName(type, value);
|
||||||
|
if (name.IsNullOrEmpty())
|
||||||
|
return Array.Empty<TAttribute>();
|
||||||
|
|
||||||
|
return type.GetField(name)?
|
||||||
|
.GetCustomAttributes(false)
|
||||||
|
.OfType<TAttribute>()
|
||||||
|
?? Array.Empty<TAttribute>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an attribute on an enum.
|
/// Gets an attribute on an enum.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -15,18 +35,8 @@ public static class EnumExtensions
|
||||||
/// <param name="value">The enum value that has an attached attribute.</param>
|
/// <param name="value">The enum value that has an attached attribute.</param>
|
||||||
/// <returns>The attached attribute, if any.</returns>
|
/// <returns>The attached attribute, if any.</returns>
|
||||||
public static TAttribute? GetAttribute<TAttribute>(this Enum value)
|
public static TAttribute? GetAttribute<TAttribute>(this Enum value)
|
||||||
where TAttribute : Attribute
|
where TAttribute : Attribute =>
|
||||||
{
|
value.GetAttributes<TAttribute>().SingleOrDefault();
|
||||||
var type = value.GetType();
|
|
||||||
var name = Enum.GetName(type, value);
|
|
||||||
if (name.IsNullOrEmpty())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return type.GetField(name)?
|
|
||||||
.GetCustomAttributes(false)
|
|
||||||
.OfType<TAttribute>()
|
|
||||||
.SingleOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an indicator if enum has been flagged as obsolete (deprecated).
|
/// Gets an indicator if enum has been flagged as obsolete (deprecated).
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 090e0c244df668454616026188c1363e5d25a1bc
|
Subproject commit cc668752416a8459a3c23345c51277e359803de8
|
||||||
Loading…
Add table
Add a link
Reference in a new issue