diff --git a/HellionChat/Branding/FoxBannerTexture.cs b/HellionChat/Branding/FoxBannerTexture.cs new file mode 100644 index 0000000..353dc31 --- /dev/null +++ b/HellionChat/Branding/FoxBannerTexture.cs @@ -0,0 +1,22 @@ +using Dalamud.Interface.Textures; + +namespace HellionChat.Branding; + +// UI sibling of HellionForgeAscii.FoxMini: the embedded Hellion Forge fox +// banner PNG. Uses ITextureProvider.GetFromManifestResource, a "Get" shared +// texture, so Dalamud owns the cache and lifetime. No manual dispose, no async +// handling in the plugin. Static to mirror HellionForgeAscii (zero injectable +// deps; Plugin.TextureProvider is a static [PluginService]). +internal static class FoxBannerTexture +{ + private const string ResourceName = "HellionChat.Branding.fox-banner.png"; + + // Resolved fresh on every access. Dalamud keeps the shared texture cached + // internally and decodes it asynchronously, so GetWrapOrDefault() returns + // null for the first few frames until the decode finishes. + public static ISharedImmediateTexture Shared => + Plugin.TextureProvider.GetFromManifestResource( + typeof(FoxBannerTexture).Assembly, + ResourceName + ); +} diff --git a/HellionChat/Branding/HellionForgeAscii.cs b/HellionChat/Branding/HellionForgeAscii.cs index da98572..7bbf04d 100644 --- a/HellionChat/Branding/HellionForgeAscii.cs +++ b/HellionChat/Branding/HellionForgeAscii.cs @@ -1,25 +1,18 @@ namespace HellionChat.Branding; -// Lazy-loaded provenance art that ships embedded with the DLL. Two -// variants: +// Lazy-loaded ASCII art that ships embedded with the DLL. // -// - FoxBanner: the full-size silhouette with "Hellion Forge" inside -// the body — rendered in the first-run wizard and the Information -// tab as a small "about the makers" anchor. // - FoxMini: the four-line fox-head + curly-tail that gets stitched // into the DI-logger bootstrap line so an xllog reader sees the // same signature on every plugin load. // -// Both files live as embedded resources under HellionChat.Branding.* so -// the plugin DLL is self-contained — no on-disk asset lookup that could +// The file lives as an embedded resource under HellionChat.Branding.* so +// the plugin DLL is self-contained; no on-disk asset lookup that could // silently miss after a partial deploy. internal static class HellionForgeAscii { - private static string? _foxBanner; private static string? _foxMini; - public static string FoxBanner => _foxBanner ??= Load("HellionChat.Branding.fox-banner.txt"); - public static string FoxMini => _foxMini ??= Load("HellionChat.Branding.fox-mini.txt"); private static string Load(string resourceName) diff --git a/HellionChat/HellionChat.csproj b/HellionChat/HellionChat.csproj index a9071a9..97467fd 100644 --- a/HellionChat/HellionChat.csproj +++ b/HellionChat/HellionChat.csproj @@ -58,8 +58,8 @@ Inter-OFL.txt - - HellionChat.Branding.fox-banner.txt + + HellionChat.Branding.fox-banner.png HellionChat.Branding.fox-mini.txt diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index aa739d9..67e2399 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -337,6 +337,7 @@ public sealed class Plugin : IAsyncDalamudPlugin new SelfTests.FontPushSmokeStep(this), new SelfTests.WizardStateSmokeStep(this), new SelfTests.QuickPickerSelfTestStep(this), + new SelfTests.FoxBannerTextureSmokeStep(this), ]); // Re-surface the wizard for existing users when a major UX diff --git a/HellionChat/Resources/Branding/fox-banner.png b/HellionChat/Resources/Branding/fox-banner.png new file mode 100644 index 0000000..135acf6 Binary files /dev/null and b/HellionChat/Resources/Branding/fox-banner.png differ diff --git a/HellionChat/Resources/Branding/fox-banner.txt b/HellionChat/Resources/Branding/fox-banner.txt deleted file mode 100644 index 8351a91..0000000 --- a/HellionChat/Resources/Branding/fox-banner.txt +++ /dev/null @@ -1,68 +0,0 @@ - .:;+xXXX$$$$$$$$XXx+;: -.X$+ .;+X$$$$$$$$$$$$$$$$$$$$$$$$$$$x: -;$xx$$X+:... .....::+X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$;. -X$; .:+xXXX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X: -$$; :++xX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X; -$$x. .+$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X. -x$$; ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+;::::::;x$$$$$: -:$$$; .+$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+:. .+$$$$$$$$$X+;;: - ;$$$+. :X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X;: :$$$$$$$$$$$$$$$$X;. - .+$$$X: ..;X$$$$$$$$$$$$$$$$$$$$$$$$$$X;.. :$$$$$$$$$$$$$$$$$$$$X: - ;$$$$$X+::::+X$$$$$$$$$$$$$$$$$$$$$X;. .$$$$$$$$$$$$$$$$$$$$$$$X; - +$$$$$$$$$$$$$$$$$$$$$$$$$$$$X+: Hellion Forge x$$$$$$$$$$$$$$$$$$$$$$$$$X: - .;x$$$$$$$$$$$$$$$$$$$$$x;: .X$$$$$$$$$$$$$$$$$$$$$$$$$$$+ - .;+$$$$$$$$$$X+;:.. .X$$$$$$$$$$$$$$$$$$$$$$$$$$$$+ - .X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$; - .X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X - x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$X - ;$$$$$$xx$$$$$$$$$$$$$$$$$$$$$x - .$$$$$$x+$$$$$$$$$$$$$$$$$$$$$x - :+X$$$$$$X;$$$$$$$$$$$$$$$$$$$$$$: - ;$$$$$$$$$$;$$$$$$$$$$$$$$$$$$$$$$X. - +$$$$$$$$$$;x$$$$$$$$$$$$$$$$$$$$$$+ - x$$$$$$$$$$:$$$$$$$$$$$$$$$$$$$$$$X: - .X$$$$$$$$$.:$$$$$$$$$$$$$$$$$$$$$$; - :X$$X;;;;: .$$$$$$$$$$$$$$$$$$$$$$X. - .$$$$X .$$$$$$$$$$$$$$$$$$$$$$$: - .$$$$+ .X$$$$$$$$$$$$$$$$$$$$$$; - ;$$$$: .X$$$$$$$$$$$$$$$$$$$$$$x - :X$$$+ .$$$$$$$$$$$$$$$$$$$$$$$X - +$$$x :$$$$$$$$$$$$$$$$$$$$$$$X - ;$$X: $$$$$$$$$$$$$$$$$$$$$$$$X - x$$$$$$$$$$$$$$$$$$$$$$$$X - +$$$$$$$$$$$$$$$$$$$$$$$$$+ - .+$$$$$$$$$$$$$$$$$$$$$$$$$$; - . ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$: - :X$x$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ - .XX$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$+ - ;$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$+$; - .. ++X$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$:+$: - :$$+. ;$$$$$$$$$$$$$$X$$$$$$$$$$$$$$$$$$$$$;:$$+ - .x+X$X: X$$$$$$$$$$x::;:;$$$$$$$$$$$$$$$$$$X: ;$X. - :X.x$$$:.::::::;x+:X$$$$;$$$$$$$$$$$$$$$$$$: :X; - :x.x$$$$$$$$$$$$$$$$$;;$:$$$$$$$$$$$$$$$$$: :$+ - :Xx$$$$$$$$$$$$$$$$$: ;X;$$$$$$$$$$$$$$$$: .+$$; - ;$$$$$$$$$$$$$$$$$$; .X+X$$$$$$$$$$$$$$$+ .+$+. - +$$$$$$$$$$$$$$$$$$$$$$$;+$$$$$$$$$$$$$$X: .+X: - +$$$$$$$$$$$$$$$$$$$$$$$$$+:$$$$$$$$$$$$$+.+$+. - ;$$$$$$$$$$$$$$$$$$$$$$$$$$$X;$$$$$$$$$$$$$$X: - +X: .:X$$$$$$$$x+++x$$$$$$$$;:X$$$$$$$$$$$X: - :x.;$;+$$$$$:. :X$$$$X :$$$$$$$$$$X: - ;x :X$$$; .x$$x X$$; .:+.$$$$$$$$$$x - xx.X$$X: X$;.:$X:.X$$$$$$$$$: - +$$$$X. ;$;::: .$$$$$$$$$: - ;$$$; :+X$$$$XX$; X$$$$$$$$: - ;$$X: .:x$x$$$$$X. x$$$$$$$$: - :X$X: :+x; :$$$$$: +$$$$$$$X: - :++$X+xXX;. +$$$$. +$$$$$$$+. - ... .X$$$X. +$$$$$$$: - ;$$$$; .X$$$$$$x. - ;$$X; :X$$$$$$; - ;$$$$$$x. - .X$$$$$$; - ;$$$$$$+ - +$$$$$; - :X$$$$;. - ;$$$$+. - .x$$$X: - .+$$X; diff --git a/HellionChat/SelfTests/FoxBannerTextureSmokeStep.cs b/HellionChat/SelfTests/FoxBannerTextureSmokeStep.cs new file mode 100644 index 0000000..45b5f87 --- /dev/null +++ b/HellionChat/SelfTests/FoxBannerTextureSmokeStep.cs @@ -0,0 +1,47 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Plugin.SelfTest; +using HellionChat.Branding; + +namespace HellionChat.SelfTests; + +// Verifies the embedded fox-banner PNG decodes into a usable texture. The load +// is async, so the step returns Waiting until Dalamud finishes the decode and +// the self-test runner re-polls. A decode or resource error is a build defect +// and fails the step hard. The resource lives in the DLL, it cannot be a +// runtime miss. +internal sealed class FoxBannerTextureSmokeStep : ISelfTestStep +{ + private readonly Plugin plugin; + + public FoxBannerTextureSmokeStep(Plugin plugin) + { + this.plugin = plugin; + } + + public string Name => "Hellion Chat - Fox banner texture smoke"; + + public SelfTestStepResult RunStep() + { + if (!FoxBannerTexture.Shared.TryGetWrap(out var wrap, out var ex)) + { + if (ex is not null) + { + ImGui.Text($"Fox banner load failed: {ex.Message}"); + return SelfTestStepResult.Fail; + } + + ImGui.Text("Fox banner still loading..."); + return SelfTestStepResult.Waiting; + } + + if (wrap.Size.X <= 0 || wrap.Size.Y <= 0) + { + ImGui.Text($"Fox banner has degenerate size {wrap.Size}"); + return SelfTestStepResult.Fail; + } + + return SelfTestStepResult.Pass; + } + + public void CleanUp() { } +} diff --git a/HellionChat/Ui/FirstRunWizard.cs b/HellionChat/Ui/FirstRunWizard.cs index c991fc3..0d81fc9 100644 --- a/HellionChat/Ui/FirstRunWizard.cs +++ b/HellionChat/Ui/FirstRunWizard.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Numerics; using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using HellionChat.Branding; @@ -171,24 +172,20 @@ public sealed class FirstRunWizard : Window ImGui.TextUnformatted(HellionStrings.Wizard_Step1_Title); ImGui.Spacing(); - // Banner is opt-in: the full silhouette dominates the wizard window - // at the default size, so the TreeNode is folded by default and the - // onboarding copy stays the primary focus. Mirrors the pre-rewrite - // collapsible anchor from v1.5.1. - using (var tree = ImRaii.TreeNode("Hellion Forge")) + // Fox-banner image: the embedded Hellion Forge fox artwork, replacing + // the ASCII FoxBanner. Async load renders nothing until the texture is + // ready (a few frames). No theme tinting, the fox keeps its identity. + var banner = FoxBannerTexture.Shared.GetWrapOrDefault(); + if (banner is not null) { - if (tree.Success) - { - using (Plugin.Interface.UiBuilder.MonoFontHandle.Push()) - { - // CalcTextSize must run inside the MonoFont push so the - // measurement matches the glyph width actually used for - // rendering. - var bannerSize = ImGui.CalcTextSize(HellionForgeAscii.FoxBanner); - ImGui.SetCursorPosX((ImGui.GetContentRegionAvail().X - bannerSize.X) * 0.5f); - ImGui.TextUnformatted(HellionForgeAscii.FoxBanner); - } - } + var height = 120f * ImGuiHelpers.GlobalScale; + var width = height * banner.Size.X / banner.Size.Y; + // Anchor the centering offset to the current cursor X. Avoids a + // negative position if the banner is ever wider than the region. + var offsetX = (ImGui.GetContentRegionAvail().X - width) * 0.5f; + if (offsetX > 0f) + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + offsetX); + ImGui.Image(banner.Handle, new Vector2(width, height)); } ImGui.Spacing(); diff --git a/HellionChat/Ui/SettingsTabs/Information.cs b/HellionChat/Ui/SettingsTabs/Information.cs index 9a2ba87..774e755 100644 --- a/HellionChat/Ui/SettingsTabs/Information.cs +++ b/HellionChat/Ui/SettingsTabs/Information.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; @@ -68,18 +69,15 @@ internal sealed class Information : ISettingsTab DrawChangelogSection(); } - // Provenance anchor — folded by default so the tab opens to the - // version-info section as before. Expands to show the full Hellion - // Forge silhouette in monospace. private void DrawHellionForgeSection() { - using var tree = ImRaii.TreeNode("Hellion Forge"); - if (!tree.Success) + var banner = FoxBannerTexture.Shared.GetWrapOrDefault(); + if (banner is null) return; - using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false)) - using (Plugin.Interface.UiBuilder.MonoFontHandle.Push()) - ImGui.TextUnformatted(HellionForgeAscii.FoxBanner); + var height = 120f * ImGuiHelpers.GlobalScale; + var width = height * banner.Size.X / banner.Size.Y; + ImGui.Image(banner.Handle, new Vector2(width, height)); } private void DrawVersionInfoSection()