Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08f1032205 | |||
| 99901b64ed | |||
| 7ef1337ea0 | |||
| a13713752e | |||
| a9f42e32c5 | |||
| 1d3b429f1b | |||
| c640a05a8a | |||
| 73a8532e26 | |||
| 32840623ff | |||
| 2acac78b4c | |||
| ce4c5d9cf9 | |||
| 4cf7aa5501 | |||
| 0da4751b0f | |||
| ee39fd0eec | |||
| 78efd654e6 | |||
| d3cea8c6c0 | |||
| 3058e6bc6d | |||
| 8a8c6ccae2 | |||
| eafa20748c | |||
| b3fc96f424 | |||
| a18ac130b3 | |||
| c652b102fc | |||
| ba4cd918da | |||
| a6e2a75422 | |||
| d05770fd6d | |||
| 921dd701c4 | |||
| ba30b1e742 | |||
| 5771573a94 | |||
| d4bcbc93e2 | |||
| ca801a006a | |||
| cc1c05add0 | |||
| 969d5e6aa6 | |||
| aaeca76bfd | |||
| 4f6c916bd9 | |||
| ce7dda9e48 | |||
| 80699b27e4 | |||
| 3296a12516 | |||
| 81123ccddf | |||
| 636a62814f | |||
| b5aebaad35 | |||
| bd75f2453c | |||
| c909d1646b | |||
| 5781be2e41 | |||
| 65fea0e5f5 | |||
| 3de6e4a3cb | |||
| e0289962b1 | |||
| 95375c8516 | |||
| 36ea8ddcfc | |||
| 246f0e2511 | |||
| 2e81c42e3b | |||
| a46d89c197 | |||
| 57b6ead003 | |||
| a42cc2a97e | |||
| 96ff4ddfd8 | |||
| 0bfe3a62cb | |||
| 01a7f9b4ec | |||
| 0237602ab7 | |||
| a600f014eb | |||
| a35067f80a | |||
| 74b07519f5 | |||
| 8dade8c4b2 | |||
| 35e8d3a7fe | |||
| 38586db9d8 | |||
| c357873604 |
@@ -35,7 +35,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
# the user supplies the tag explicitly. Always check out that tag so
|
||||
# the yaml + forge-posts file are read from the tagged tree, not main.
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
@@ -147,10 +147,14 @@ jobs:
|
||||
}
|
||||
Write-Host "Embed-Caps OK: de=$($deDesc.Length)/4096, en=$($enDesc.Length)/4096, total=$totalChars/6000"
|
||||
|
||||
# ---------- Embed-Payload bauen (zwei Embeds, gleiche url) ----------
|
||||
# Sharing the same `url` tells Discord to render both embeds as a
|
||||
# single contiguous card block. The title sits on the first embed,
|
||||
# the footer + timestamp on the last so it reads as one post.
|
||||
# ---------- Embed-Payload bauen (zwei gestapelte Embeds) ----------
|
||||
# Discord MERGES embeds in one message that share the same `url`
|
||||
# (the image-gallery merge) and then renders only the FIRST embed's
|
||||
# description — every following embed contributes images only. So
|
||||
# only the DE embed carries the release URL; the EN embed stays
|
||||
# url-less, which makes Discord stack both as separate cards with
|
||||
# both descriptions visible. Title sits on the first embed, footer
|
||||
# + timestamp on the last so it still reads as one post.
|
||||
$payload = [ordered]@{
|
||||
username = "Forge Herald"
|
||||
avatar_url = "https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/raw/branch/main/HellionChat/images/icon.png"
|
||||
@@ -167,7 +171,8 @@ jobs:
|
||||
description = $deDesc
|
||||
},
|
||||
[ordered]@{
|
||||
url = $releaseUrl
|
||||
# Deliberately no `url` — a shared url would make Discord
|
||||
# merge this embed into the first and drop the EN body.
|
||||
color = 12730636
|
||||
description = $enDesc
|
||||
footer = [ordered]@{ text = $footerText }
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup .NET 10
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5
|
||||
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5
|
||||
with:
|
||||
dotnet-version: 10.0.x
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
subtitle: "24 Sprachen, Inter Light statt Exo 2, HITCH 74 → 20 ms"
|
||||
versionsnatur: "Localisation + Font-Stack"
|
||||
---
|
||||
- **24 wählbare UI-Sprachen.** Aus dem ursprünglich nur als FR-Lokalisierung geplanten Cycle ist eine breite Welle geworden: Catalan, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hungarian, Italian, Japanese, Korean, Norsk bokmål, Polish, Portuguese (BR), Portuguese (PT), Romanian, Russian, Spanish, Swedish, Turkish, Ukrainian, Simplified Chinese, Traditional Chinese. Dropdown sortiert alphabetisch nach Endonym, „None" oben angepinnt. Nicht-native Übersetzungen sind AI-assisted und für Community-Review im Forge-Discord markiert.
|
||||
- **Inter Light statt Exo 2 als bundled Schrift.** Plus NotoSansCjkRegular als dritte Merge-Schicht. Damit deckt der Stack Latin Extended-A/B, Greek polytonic, Cyrillic Supplement und CJK (inkl. Hangul, Simplified-Han nach Reform) ab — die nicht-vanilla-FFXIV-Sprachen waren mit Exo 2 nicht lesbar.
|
||||
- **HITCH 74 → ~20 ms als Side-Effect.** Der UiBuilder-First-Frame-Lag lag seit v1.4.x stabil bei 74 ms; v1.5.1 wollte ihn in Richtung 7 ms ziehen, fiel als „Hypothese zu optimistisch" durch. Echter Grund: `Plugin.cs:937` push'te `RegularFont` nur wenn `FontsEnabled` true war — die „Mitgelieferte Schrift verwenden"-Logik setzte `FontsEnabled = false` mit, der bundled-Pfad war die ganze v1.5.x-Reihe tot, FFXIVs Axis-Font übernahm und kostete ~50 ms extra. Fix routet `RegularFont` jetzt auch über `UseHellionFont`. Median ~20 ms im 5-Reload-Stresstest (17.9-23.6 ms, Linux/Wine; Windows-Baseline steht aus).
|
||||
- **Glyph-Ranges aktivieren sich automatisch beim Sprachwechsel** plus eine One-Shot-Migration für User die schon eine non-Latin-Sprache eingestellt hatten. Neue WarningText unter dem Sprach-Dropdown weist darauf hin, dass FFXIVs Chat-Engine offiziell nur EN/DE/FR/JA-Glyphen rendert — andere Schriften können in der Game-Eingabe Garbled-Output zeigen.
|
||||
- **Unter der Haube.** Drei-Layer-Font-Stack, zwei neue ExtraGlyphRanges-Flags (`LatinExtended`, `Greek`), `LanguageOverride`-Enum wächst um zehn Locales plus drei reaktivierte (Italian, Korean, Norwegian mit `nb`). Append-only damit User-Configs stabil bleiben. Migration v17 bleibt.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
subtitle: "Theme-Crossfade, Quick-Picker, Hover-Animationen"
|
||||
versionsnatur: "Polish & Motion"
|
||||
---
|
||||
- **Theme-Crossfade.** Theme-Wechsel blenden jetzt sanft über rund 300 ms ineinander, statt hart umzuschalten. Alle Hellion-Flächen gleiten mit: Sidebar, Titel, Buttons, Tabs, Scrollbar, Trennlinien. Der Fenster-Hintergrund snappt bewusst weiter, damit das Per-Window-Deckkraft-Setting aus Dalamuds Pinning-Menü unangetastet bleibt.
|
||||
- **Header-Quick-Picker.** Neuer Paletten-Button links vom Zahnrad im Chat-Header. Ein Klick öffnet ein kompaktes Popup mit zwei Sektionen: alle Built-in- und Custom-Themes sowie alle Tabs. Der aktive Eintrag trägt ein Häkchen, ein Klick wechselt ohne das Popup zu schließen. So lassen sich mehrere Wechsel hintereinander erledigen, ohne den Umweg über die Einstellungen.
|
||||
- **Sanfte Hover-Animationen.** Sidebar-Icons faden bei Hover sanft von gedimmt auf volle Deckkraft. Card-Mode-Trennlinien heben sich beim Überfahren einer Zeile für den ganzen Tab dezent ab. Beides framerate-unabhängig gerechnet, also auch bei Wine-Stall-Frames stabil.
|
||||
- **Bewegung reduzieren.** Neuer Toggle im Tab für Theme und Layout. Er deaktiviert Crossfade, Hover-Animationen und das Pulsieren ungelesener Tabs für alle, die eine statische Oberfläche bevorzugen.
|
||||
- Drei P3-Items plus der Accessibility-Toggle, kein Schema-Bump, keine Migration. Eine kleine Polish-Welle vor den größeren Cycles.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
subtitle: "Backlog-Sync Tab-Features"
|
||||
versionsnatur: "Bundle-Patch (Hälfte 1 von 2)"
|
||||
---
|
||||
- **Fehlgeschlagener Tell.** Geht ein gesendeter Tell nicht durch (Empfänger offline, in einer Instanz oder blockiert), erscheint jetzt ein Warn-Toast statt dass die Systemmeldung durchrauscht. Abschaltbar in den Einstellungen unter Chat.
|
||||
- **Ton pro Tab.** Jeder Chat-Tab kann einen Benachrichtigungston spielen, wenn eine Nachricht eintrifft, während ein anderer Tab aktiv ist. Zur Wahl stehen die 16 Spiel-Chat-Sounds oder drei mitgelieferte Hellion-Sounds, mit einem Vorhör-Knopf. Standardmäßig aus, hört auf den globalen Sound-Schalter.
|
||||
- **Tab umbenennen.** Das Umbenennen-Feld im Rechtsklick-Menü fokussiert sich beim Öffnen von selbst und nimmt jetzt bis zu 512 Zeichen.
|
||||
- **Sprung ans Ende.** In der Chat-Kopfleiste erscheint ein Knopf, sobald man vom aktuellen Ende weggescrollt ist. Ein Klick springt zurück zur jüngsten Nachricht.
|
||||
- **Karten- und Item-Links.** Kartenmarkierung und verlinktes Item lassen sich aus dem Rechtsklick-Menü der Chat-Eingabe einfügen.
|
||||
- **Fuchs-Banner.** Das Hellion-Forge-Fuchs-Motiv im Einrichtungs-Assistenten und im Informations-Tab ist jetzt ein echtes Bild statt ASCII-Kunst.
|
||||
- Schema-Bump auf v18, rein additiv.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
subtitle: "Settings Overhaul + Filter & Notification Polish"
|
||||
versionsnatur: "Settings-Overhaul-Release"
|
||||
---
|
||||
- **Settings komplett neu strukturiert** — die zehn alten Tabs sind auf sieben zusammengefasst (Allgemein, Aussehen, Chat, Fenster, Kanäle, Daten & Privatsphäre, Über). Jeder Tab gliedert sich jetzt in Sektionen, die beim Reingehen eingeklappt sind. Controls innerhalb einer Sektion sind nach Typ gruppiert. Tabs-Tab im Per-Tab-Panel ebenfalls in Sub-Sektionen aufgeteilt.
|
||||
- **Absender-Namen anpassbar** — neue Optionen in Chat → Nachrichten für das Namensformat (Voll / Vorname / Initialen) und das Welt-Suffix (Nie / Andere Welten / Immer).
|
||||
- **Pre-Send-Warnung für Plugin-Symbole** — beim Senden einer Nachricht mit Symbolen, die nur HellionChat-User sehen, kommt eine Warnung. Verhindert leere Kästchen bei anderen.
|
||||
- **Getrennte Fenster-Deckkraft** — Aktiv vs. Inaktiv. Aktiv wie bisher; Inaktiv über einen zweiten Slider unter Aussehen → Fenster-Stil.
|
||||
- **Lautstärke für eigene Notification-Sounds** — Slider in Allgemein → Sound, im Kanäle-Tab pro Tab nochmal angezeigt. Wirkt nur auf die drei mitgelieferten Custom-Sounds, die 16 Game-Sounds bleiben unverändert.
|
||||
- **Regex-Filter pro Tab gestrichen** — kurz dabei, dann verworfen: der eingebaute FFXIV-Blackword-Filter deckt das ab.
|
||||
- **Lokalisierung erweitert** — neue Section-Titel und v1.5.6-Controls in allen 24 Sprachen, maschinell übersetzt. Native-Review läuft weiter über den Hellion Forge Discord.
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+122
-12
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Configuration;
|
||||
@@ -34,7 +35,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int LatestVersion = 17;
|
||||
private const int LatestVersion = 19;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -44,6 +45,10 @@ public class Configuration : IPluginConfiguration
|
||||
// Global window opacity, applied across all themes.
|
||||
public float WindowOpacity = 0.85f;
|
||||
|
||||
// UI-12: background opacity of the main chat window while unfocused.
|
||||
// WindowOpacity above stays the focused value.
|
||||
public float WindowOpacityInactive = 0.65f;
|
||||
|
||||
// Reserved for future UI toggles; pre-declared to avoid a migration later.
|
||||
public bool ReduceMotion;
|
||||
|
||||
@@ -133,6 +138,10 @@ public class Configuration : IPluginConfiguration
|
||||
public bool SeenPopOutHeaderHint;
|
||||
public bool AutoTellTabsOpenAsPopout;
|
||||
|
||||
// UI-7: how sender names are rendered in the chat log.
|
||||
public WorldSuffixMode WorldSuffixMode = WorldSuffixMode.OtherWorldOnly;
|
||||
public NameFormMode NameFormMode = NameFormMode.Full;
|
||||
|
||||
public int GetRetentionDays(ChatType type)
|
||||
{
|
||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||
@@ -187,6 +196,15 @@ public class Configuration : IPluginConfiguration
|
||||
public bool CollapseKeepUniqueLinks;
|
||||
public bool SymbolPickerEnabled = true;
|
||||
public bool PlaySounds = true;
|
||||
|
||||
// AUDIO-1: playback volume (0-1) for the three bundled custom sounds.
|
||||
public float CustomSoundVolume = 0.5f;
|
||||
|
||||
// Toast when a tell the user sent could not be delivered.
|
||||
public bool NotifyFailedTell = true;
|
||||
|
||||
// UI-11: warn before sending a message that carries plugin-only glyphs.
|
||||
public bool NotifyPluginDisclosure = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public int MaxLinesToRender = 2_500; // 1-10000
|
||||
public bool Use24HourClock = true;
|
||||
@@ -282,6 +300,9 @@ public class Configuration : IPluginConfiguration
|
||||
CollapseKeepUniqueLinks = other.CollapseKeepUniqueLinks;
|
||||
SymbolPickerEnabled = other.SymbolPickerEnabled;
|
||||
PlaySounds = other.PlaySounds;
|
||||
CustomSoundVolume = other.CustomSoundVolume;
|
||||
NotifyFailedTell = other.NotifyFailedTell;
|
||||
NotifyPluginDisclosure = other.NotifyPluginDisclosure;
|
||||
KeepInputFocus = other.KeepInputFocus;
|
||||
MaxLinesToRender = other.MaxLinesToRender;
|
||||
Use24HourClock = other.Use24HourClock;
|
||||
@@ -353,6 +374,7 @@ public class Configuration : IPluginConfiguration
|
||||
// v1.1.0 theme engine fields
|
||||
Theme = other.Theme;
|
||||
WindowOpacity = other.WindowOpacity;
|
||||
WindowOpacityInactive = other.WindowOpacityInactive;
|
||||
ReduceMotion = other.ReduceMotion;
|
||||
UseCompactDensity = other.UseCompactDensity;
|
||||
|
||||
@@ -367,6 +389,9 @@ public class Configuration : IPluginConfiguration
|
||||
PopOutInputEnabled = other.PopOutInputEnabled;
|
||||
SeenPopOutHeaderHint = other.SeenPopOutHeaderHint;
|
||||
AutoTellTabsOpenAsPopout = other.AutoTellTabsOpenAsPopout;
|
||||
|
||||
WorldSuffixMode = other.WorldSuffixMode;
|
||||
NameFormMode = other.NameFormMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +468,10 @@ public class Tab
|
||||
public bool AllSenderMessages;
|
||||
public TellTarget TellTarget = TellTarget.Empty();
|
||||
|
||||
// Per-tab notification sound for messages arriving in an inactive tab.
|
||||
public bool EnableNotificationSound;
|
||||
public uint NotificationSoundId = 1;
|
||||
|
||||
[NonSerialized]
|
||||
public uint Unread;
|
||||
|
||||
@@ -485,6 +514,17 @@ public class Tab
|
||||
[NonSerialized]
|
||||
internal string? _cachedTellIcon;
|
||||
|
||||
// PM-3 hover-lerp state. Default 0f means "not hovered". Sidebar
|
||||
// path animates per tab; card-mode-border path is tab-aggregate
|
||||
// (any card-row hover ramps the alpha for all cards in this tab).
|
||||
// Lerp speed lives in the render loop, not here, so the same field
|
||||
// serves both sites at the same animation curve.
|
||||
[NonSerialized]
|
||||
internal float _hoverAlpha;
|
||||
|
||||
[NonSerialized]
|
||||
internal float _cardHoverAlpha;
|
||||
|
||||
public bool Matches(Message message)
|
||||
{
|
||||
if (!message.Matches(SelectedChannels, ExtraChatAll, ExtraChatChannels))
|
||||
@@ -550,6 +590,8 @@ public class Tab
|
||||
IsPinned = IsPinned,
|
||||
AllSenderMessages = AllSenderMessages,
|
||||
TellTarget = TellTarget.Clone(),
|
||||
EnableNotificationSound = EnableNotificationSound,
|
||||
NotificationSoundId = NotificationSoundId,
|
||||
IsGreeted = IsGreeted,
|
||||
};
|
||||
}
|
||||
@@ -833,17 +875,27 @@ public enum LanguageOverride
|
||||
French,
|
||||
German,
|
||||
Greek,
|
||||
|
||||
// Italian,
|
||||
Japanese,
|
||||
|
||||
// Korean,
|
||||
// Norwegian,
|
||||
PortugueseBrazil,
|
||||
Romanian,
|
||||
Russian,
|
||||
Spanish,
|
||||
Swedish,
|
||||
|
||||
// v1.5.3: Crowdin-heritage activated and Forge-maintained additions.
|
||||
// Append-only to preserve serialized integer values of existing user configs.
|
||||
Italian,
|
||||
Korean,
|
||||
Norwegian,
|
||||
Catalan,
|
||||
Czech,
|
||||
Danish,
|
||||
Finnish,
|
||||
Hungarian,
|
||||
Polish,
|
||||
PortuguesePortugal,
|
||||
Turkish,
|
||||
Ukrainian,
|
||||
}
|
||||
|
||||
public static class LanguageOverrideExt
|
||||
@@ -859,15 +911,24 @@ public static class LanguageOverrideExt
|
||||
LanguageOverride.French => "Français",
|
||||
LanguageOverride.German => "Deutsch",
|
||||
LanguageOverride.Greek => "Ελληνικά",
|
||||
// LanguageOverride.Italian => "Italiano",
|
||||
LanguageOverride.Italian => "Italiano",
|
||||
LanguageOverride.Japanese => "日本語",
|
||||
// LanguageOverride.Korean => "한국어 (Korean)",
|
||||
// LanguageOverride.Norwegian => "Norsk",
|
||||
LanguageOverride.Korean => "한국어",
|
||||
LanguageOverride.Norwegian => "Norsk bokmål",
|
||||
LanguageOverride.PortugueseBrazil => "Português do Brasil",
|
||||
LanguageOverride.Romanian => "Română",
|
||||
LanguageOverride.Russian => "Русский",
|
||||
LanguageOverride.Spanish => "Español",
|
||||
LanguageOverride.Swedish => "Svenska",
|
||||
LanguageOverride.Catalan => "Català",
|
||||
LanguageOverride.Czech => "Čeština",
|
||||
LanguageOverride.Danish => "Dansk",
|
||||
LanguageOverride.Finnish => "Suomi",
|
||||
LanguageOverride.Hungarian => "Magyar",
|
||||
LanguageOverride.Polish => "Polski",
|
||||
LanguageOverride.PortuguesePortugal => "Português (Portugal)",
|
||||
LanguageOverride.Turkish => "Türkçe",
|
||||
LanguageOverride.Ukrainian => "Українська",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
||||
};
|
||||
|
||||
@@ -882,17 +943,47 @@ public static class LanguageOverrideExt
|
||||
LanguageOverride.French => "fr",
|
||||
LanguageOverride.German => "de",
|
||||
LanguageOverride.Greek => "el",
|
||||
// LanguageOverride.Italian => "it",
|
||||
LanguageOverride.Italian => "it",
|
||||
LanguageOverride.Japanese => "ja",
|
||||
// LanguageOverride.Korean => "ko",
|
||||
// LanguageOverride.Norwegian => "no",
|
||||
LanguageOverride.Korean => "ko",
|
||||
LanguageOverride.Norwegian => "nb",
|
||||
LanguageOverride.PortugueseBrazil => "pt-br",
|
||||
LanguageOverride.Romanian => "ro",
|
||||
LanguageOverride.Russian => "ru",
|
||||
LanguageOverride.Spanish => "es",
|
||||
LanguageOverride.Swedish => "sv",
|
||||
LanguageOverride.Catalan => "ca",
|
||||
LanguageOverride.Czech => "cs",
|
||||
LanguageOverride.Danish => "da",
|
||||
LanguageOverride.Finnish => "fi",
|
||||
LanguageOverride.Hungarian => "hu",
|
||||
LanguageOverride.Polish => "pl",
|
||||
LanguageOverride.PortuguesePortugal => "pt-pt",
|
||||
LanguageOverride.Turkish => "tr",
|
||||
LanguageOverride.Ukrainian => "uk",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
|
||||
};
|
||||
|
||||
// Maps a language to the ExtraGlyphRanges flag required for full UI
|
||||
// rendering in that locale. The settings save path ORs this into
|
||||
// Mutable.ExtraGlyphRanges so users do not need to know which range
|
||||
// to tick manually. Returns 0 for locales fully covered by the default
|
||||
// ImGui glyph range (Latin-1) or by the separate Japanese font handle.
|
||||
public static ExtraGlyphRanges RequiredGlyphRanges(this LanguageOverride mode) =>
|
||||
mode switch
|
||||
{
|
||||
LanguageOverride.Korean => ExtraGlyphRanges.Korean,
|
||||
LanguageOverride.ChineseSimplified => ExtraGlyphRanges.ChineseSimplifiedCommon,
|
||||
LanguageOverride.ChineseTraditional => ExtraGlyphRanges.ChineseFull,
|
||||
LanguageOverride.Ukrainian => ExtraGlyphRanges.Cyrillic,
|
||||
LanguageOverride.Greek => ExtraGlyphRanges.Greek,
|
||||
LanguageOverride.Czech
|
||||
or LanguageOverride.Polish
|
||||
or LanguageOverride.Romanian
|
||||
or LanguageOverride.Hungarian
|
||||
or LanguageOverride.Turkish => ExtraGlyphRanges.LatinExtended,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
@@ -906,10 +997,23 @@ public enum ExtraGlyphRanges
|
||||
Korean = 1 << 4,
|
||||
Thai = 1 << 5,
|
||||
Vietnamese = 1 << 6,
|
||||
|
||||
// v1.5.3: Custom ranges for languages with Latin Extended-A glyphs (Czech,
|
||||
// Polish, Romanian, Turkish, Hungarian) and Greek polytonic accents.
|
||||
LatinExtended = 1 << 7,
|
||||
Greek = 1 << 8,
|
||||
}
|
||||
|
||||
public static class ExtraGlyphRangesExt
|
||||
{
|
||||
// Custom (start, end) inclusive pair lists for ranges that ImGui does
|
||||
// not ship a built-in helper for. SetUpRanges() feeds these into
|
||||
// ImFontGlyphRangesBuilder.AddChar via the `chars` parameter of
|
||||
// BuildRange so we avoid the lifetime/pinning question that the native
|
||||
// GetGlyphRanges*-pointer pathway papers over.
|
||||
internal static readonly ushort[] LatinExtendedPairs = { 0x0100, 0x024F };
|
||||
internal static readonly ushort[] GreekPairs = { 0x0370, 0x03FF, 0x1F00, 0x1FFF };
|
||||
|
||||
public static string Name(this ExtraGlyphRanges ranges) =>
|
||||
ranges switch
|
||||
{
|
||||
@@ -921,6 +1025,8 @@ public static class ExtraGlyphRangesExt
|
||||
ExtraGlyphRanges.Korean => Language.ExtraGlyphRanges_Korean_Name,
|
||||
ExtraGlyphRanges.Thai => Language.ExtraGlyphRanges_Thai_Name,
|
||||
ExtraGlyphRanges.Vietnamese => Language.ExtraGlyphRanges_Vietnamese_Name,
|
||||
ExtraGlyphRanges.LatinExtended => Language.ExtraGlyphRanges_LatinExtended_Name,
|
||||
ExtraGlyphRanges.Greek => Language.ExtraGlyphRanges_Greek_Name,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
||||
};
|
||||
|
||||
@@ -935,6 +1041,10 @@ public static class ExtraGlyphRangesExt
|
||||
ExtraGlyphRanges.Korean => (nint)ImGui.GetIO().Fonts.GetGlyphRangesKorean(),
|
||||
ExtraGlyphRanges.Thai => (nint)ImGui.GetIO().Fonts.GetGlyphRangesThai(),
|
||||
ExtraGlyphRanges.Vietnamese => (nint)ImGui.GetIO().Fonts.GetGlyphRangesVietnamese(),
|
||||
// LatinExtended and Greek are applied via builder.AddChar in
|
||||
// FontManager.SetUpRanges, not through a native pointer range.
|
||||
ExtraGlyphRanges.LatinExtended => 0,
|
||||
ExtraGlyphRanges.Greek => 0,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(ranges), ranges, null),
|
||||
};
|
||||
}
|
||||
|
||||
+73
-17
@@ -9,7 +9,7 @@ using Dalamud.Plugin;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// Two LogProxy sites live in static methods (TryGetHellionFontBytes,
|
||||
// Two LogProxy sites live in static methods (TryGetBundledFontBytes,
|
||||
// AddFontWithFallback); a ctor-injected ILogger would not be reachable
|
||||
// from those scopes, so the class stays on Plugin.LogProxy.
|
||||
//
|
||||
@@ -62,8 +62,8 @@ public sealed class FontManager : IDisposable
|
||||
90f,
|
||||
];
|
||||
|
||||
// Hellion font bytes (Exo 2, OFL-1.1); lazily loaded from manifest resources
|
||||
private static byte[]? HellionFontBytes;
|
||||
// Bundled UI font bytes (Inter Light, OFL-1.1); lazily loaded from manifest resources
|
||||
private static byte[]? BundledFontBytes;
|
||||
|
||||
public FontManager(IDalamudPluginInterface pluginInterface)
|
||||
{
|
||||
@@ -122,7 +122,7 @@ public sealed class FontManager : IDisposable
|
||||
e.OnPreBuild(tk =>
|
||||
{
|
||||
// UseHellionFont swaps the source font but keeps the size
|
||||
// selector tied to FontSizeV2 (the Hellion font ships as
|
||||
// selector tied to FontSizeV2 (the bundled font ships as
|
||||
// a single weight).
|
||||
var basePt = Plugin.Config.UseHellionFont
|
||||
? Plugin.Config.FontSizeV2
|
||||
@@ -130,15 +130,28 @@ public sealed class FontManager : IDisposable
|
||||
var config = new SafeFontConfig { SizePt = basePt, GlyphRanges = Ranges };
|
||||
// Missing embedded resource falls back to the configured
|
||||
// system font instead of taking the whole UiBuilder down.
|
||||
var hellionBytes = Plugin.Config.UseHellionFont ? TryGetHellionFontBytes() : null;
|
||||
config.MergeFont = hellionBytes is not null
|
||||
? tk.AddFontFromMemory(hellionBytes, config, "Hellion-Exo2")
|
||||
var bundledBytes = Plugin.Config.UseHellionFont ? TryGetBundledFontBytes() : null;
|
||||
config.MergeFont = bundledBytes is not null
|
||||
? tk.AddFontFromMemory(bundledBytes, config, "Inter-Light")
|
||||
: AddFontWithFallback(tk, Plugin.Config.GlobalFontV2.FontId, config, "global");
|
||||
|
||||
config.SizePt = Plugin.Config.JapaneseFontV2.SizePt;
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback covers Hangul, Simplified-Chinese
|
||||
// -specific Han (e.g. 简) and other CJK glyphs that the primary
|
||||
// (Inter Light / global font) and the FFXIV Japanese font do not
|
||||
// ship. Merged last so earlier fonts win for shared codepoints.
|
||||
config.SizePt = basePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
@@ -166,6 +179,16 @@ public sealed class FontManager : IDisposable
|
||||
config.GlyphRanges = JpRange;
|
||||
AddFontWithFallback(tk, Plugin.Config.JapaneseFontV2.FontId, config, "japanese");
|
||||
|
||||
// v1.5.3: NotoSansCjk fallback (see BuildRegularFontHandle).
|
||||
config.SizePt = Plugin.Config.ItalicFontV2.SizePt;
|
||||
config.GlyphRanges = Ranges;
|
||||
AddFontWithFallback(
|
||||
tk,
|
||||
new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
config,
|
||||
"noto-cjk-fallback"
|
||||
);
|
||||
|
||||
config.SizePt = Plugin.Config.SymbolsFontSizeV2;
|
||||
tk.AddGameSymbol(config);
|
||||
|
||||
@@ -187,26 +210,26 @@ public sealed class FontManager : IDisposable
|
||||
// happen on a signed release build, but a broken csproj or hand-rolled
|
||||
// dev build can land here. Caller falls back to the system font path
|
||||
// so the plugin still loads instead of crashing the whole UiBuilder.
|
||||
private static byte[]? TryGetHellionFontBytes()
|
||||
private static byte[]? TryGetBundledFontBytes()
|
||||
{
|
||||
if (HellionFontBytes is not null)
|
||||
return HellionFontBytes;
|
||||
if (BundledFontBytes is not null)
|
||||
return BundledFontBytes;
|
||||
|
||||
using var stream = typeof(FontManager).Assembly.GetManifestResourceStream(
|
||||
"HellionFont.ttf"
|
||||
"Inter-Light.ttf"
|
||||
);
|
||||
if (stream is null)
|
||||
{
|
||||
Plugin.LogProxy.Warning(
|
||||
"Hellion font resource missing — falling back to system default font."
|
||||
"Bundled Inter Light font resource missing, falling back to system default font."
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
HellionFontBytes = ms.ToArray();
|
||||
return HellionFontBytes;
|
||||
BundledFontBytes = ms.ToArray();
|
||||
return BundledFontBytes;
|
||||
}
|
||||
|
||||
private unsafe void SetUpRanges()
|
||||
@@ -239,6 +262,18 @@ public sealed class FontManager : IDisposable
|
||||
builder.AddText("Œœ");
|
||||
builder.AddText("ĂăÂâÎîȘșȚț");
|
||||
|
||||
// v1.5.3: language-dropdown endonyms. The dropdown renders
|
||||
// with the currently active font range; without these glyphs
|
||||
// a user on an English UI cannot read non-Latin language names
|
||||
// before switching. Auto-activation in Settings.Apply then
|
||||
// pulls in the full ExtraGlyphRange for the chosen locale.
|
||||
builder.AddText(
|
||||
"Català Čeština Dansk Deutsch Ελληνικά English Español Suomi"
|
||||
+ " Français Magyar Italiano 日本語 한국어 Norsk bokmål Nederlands"
|
||||
+ " Polski Português Brasil (Portugal) Română Русский Svenska"
|
||||
+ " Türkçe Українська 简体中文 繁體中文"
|
||||
);
|
||||
|
||||
// "Enclosed Alphanumerics" (partial) https://www.compart.com/en/unicode/block/U+2460
|
||||
for (var i = 0x2460; i <= 0x24B5; i++)
|
||||
builder.AddChar((char)i);
|
||||
@@ -248,11 +283,32 @@ public sealed class FontManager : IDisposable
|
||||
}
|
||||
|
||||
var ranges = new List<nint> { (nint)ImGui.GetIO().Fonts.GetGlyphRangesDefault() };
|
||||
var customChars = new List<ushort>();
|
||||
foreach (var extraRange in Enum.GetValues<ExtraGlyphRanges>())
|
||||
if (Plugin.Config.ExtraGlyphRanges.HasFlag(extraRange))
|
||||
ranges.Add(extraRange.Range());
|
||||
{
|
||||
if (!Plugin.Config.ExtraGlyphRanges.HasFlag(extraRange))
|
||||
continue;
|
||||
|
||||
Ranges = BuildRange(null, ranges.ToArray());
|
||||
// LatinExtended and Greek use AddChar pairs because they have no
|
||||
// built-in ImGui range helper; everything else points to a native
|
||||
// ImGui glyph-range table.
|
||||
switch (extraRange)
|
||||
{
|
||||
case ExtraGlyphRanges.LatinExtended:
|
||||
customChars.AddRange(ExtraGlyphRangesExt.LatinExtendedPairs);
|
||||
break;
|
||||
case ExtraGlyphRanges.Greek:
|
||||
customChars.AddRange(ExtraGlyphRangesExt.GreekPairs);
|
||||
break;
|
||||
default:
|
||||
var ptr = extraRange.Range();
|
||||
if (ptr != 0)
|
||||
ranges.Add(ptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ranges = BuildRange(customChars.Count > 0 ? customChars : null, ranges.ToArray());
|
||||
JpRange = BuildRange(GlyphRangesJapanese.GlyphRanges);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Dalamud.NET.Sdk/15.0.0">
|
||||
<PropertyGroup>
|
||||
<!-- Independent versioning; see yaml changelog for upstream Chat 2 base -->
|
||||
<Version>1.5.2</Version>
|
||||
<Version>1.5.6</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- Use lock file to pin exact versions -->
|
||||
@@ -14,7 +14,7 @@
|
||||
<ItemGroup>
|
||||
<!-- Closed ranges prevent surprise major bumps during lock file regeneration -->
|
||||
<PackageReference Include="MessagePack" Version="[3.1.4, 4.0.0)" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.7" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.8" />
|
||||
<!-- v1.5.0 DI-container foundation; matches Lightless pin (Hosting 10.0.7) -->
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.DependencyInjection"
|
||||
@@ -26,6 +26,11 @@
|
||||
<!-- SQLitePCLRaw override for CVE-2025-6965, CVE-2025-7709 (SQLite >= 3.50.3) -->
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
|
||||
<PackageReference Include="morelinq" Version="4.4.0" />
|
||||
<!-- NAudio.WinMM 2.2.1 MIT - WaveOutEvent/WinMM path is Wine-safe (WaveOut works under Wine,
|
||||
Media-Foundation-based codecs do not). Using the sub-package avoids pulling in
|
||||
NAudio.WinForms (which requires WindowsDesktop and does not build on Linux hosts).
|
||||
WaveOutEvent and WaveFileReader both live in NAudio.WinMM + NAudio.Core. -->
|
||||
<PackageReference Include="NAudio.WinMM" Version="2.3.0" />
|
||||
<PackageReference Include="Pidgin" Version="[3.5.1, 4.0.0)" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="[3.1.12, 4.0.0)" />
|
||||
</ItemGroup>
|
||||
@@ -50,16 +55,26 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Embedded resources: Hellion font (Exo 2, OFL-1.1) + manifest resource -->
|
||||
<!-- Embedded resources: bundled UI font (Inter Light, OFL-1.1) + manifest resource -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\HellionFont.ttf">
|
||||
<LogicalName>HellionFont.ttf</LogicalName>
|
||||
<EmbeddedResource Include="Resources\Inter-Light.ttf">
|
||||
<LogicalName>Inter-Light.ttf</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\HellionFont-OFL.txt">
|
||||
<LogicalName>HellionFont-OFL.txt</LogicalName>
|
||||
<EmbeddedResource Include="Resources\Inter-OFL.txt">
|
||||
<LogicalName>Inter-OFL.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-banner.txt">
|
||||
<LogicalName>HellionChat.Branding.fox-banner.txt</LogicalName>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-banner.png">
|
||||
<LogicalName>HellionChat.Branding.fox-banner.png</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<!-- Bundled custom notification sounds, Mono 44.1 kHz 16-bit PCM WAV (Wine-safe) -->
|
||||
<EmbeddedResource Include="Resources\Sounds\notification-1.wav">
|
||||
<LogicalName>HellionChat.Sounds.notification-1.wav</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Sounds\notification-2.wav">
|
||||
<LogicalName>HellionChat.Sounds.notification-2.wav</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Sounds\notification-3.wav">
|
||||
<LogicalName>HellionChat.Sounds.notification-3.wav</LogicalName>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Resources\Branding\fox-mini.txt">
|
||||
<LogicalName>HellionChat.Branding.fox-mini.txt</LogicalName>
|
||||
|
||||
+120
-170
@@ -15,8 +15,8 @@ description: |-
|
||||
- Per-channel retention with a daily background sweep
|
||||
- Retroactive cleanup (Ctrl+Shift confirm)
|
||||
- Export to Markdown, JSON or CSV
|
||||
- First-run wizard with three preset profiles
|
||||
- Bilingual UI (EN/DE) with live language switching
|
||||
- First-run wizard with four preset profiles
|
||||
- Multi-language UI (24 locales) with live language switching
|
||||
- Own config and database — no shared state with other plugins
|
||||
|
||||
Based on Chat 2 by Infi and Anna (EUPL-1.2).
|
||||
@@ -35,193 +35,143 @@ tags:
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**v1.5.2 — First-Run Wizard Rework (2026-05-18)**
|
||||
**v1.5.6 — Settings Overhaul + Filter & Notification Polish (2026-05-23)**
|
||||
|
||||
UX patch. The first-run wizard becomes a four-step flow with a
|
||||
new Roleplay privacy profile and a power-settings step that
|
||||
surfaces previously-hidden defaults. Existing v1.5.1 users see
|
||||
the new wizard once on first v1.5.2 boot.
|
||||
- Settings window reorganised: ten tabs down to seven (General, Appearance, Chat, Window, Channels, Data & Privacy, About). Each tab now uses collapsible sections grouped by control type. Sections start collapsed every time you open a tab — less noise, easier to find what you need.
|
||||
- New sender-name display options under Chat → Messages: separate world-suffix and name-format modes (Full name / First name only / Initials × Never / Other worlds only / Always).
|
||||
- Plugin-only symbols now show a pre-send warning so other players do not get empty boxes (Chat → Messages → "Warn before sending plugin-only symbols").
|
||||
- Separate window opacity for focused vs. inactive chat window (Appearance → Window style → "Inactive window opacity"). The slider above sets the focused value.
|
||||
- Custom notification sound volume slider (General → Sound, and mirrored in Channels → per-tab → Notification). Affects only the three bundled custom sounds; the 16 game sounds are unaffected.
|
||||
- The per-tab regex filter that briefly shipped earlier in this cycle has been removed — FFXIV's built-in blackword filter covers the same need.
|
||||
- All 24 locale files updated for the new section labels and the v1.5.6 control labels (machine translation; native review continues via the Hellion Forge Discord).
|
||||
|
||||
What changes user-visible:
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
- Wizard navigation: Welcome → Privacy profile → Power settings
|
||||
→ Done. Forge-Bronze pagination dots, dedicated stage for the
|
||||
power settings so they are no longer buried in Settings.
|
||||
- Fourth privacy profile "Roleplay": Privacy-First plus Say and
|
||||
both emote types, with a 30-day window for Say and a 90-day
|
||||
window for emotes. Shout, Yell and Novice Network stay out.
|
||||
- Privacy picker becomes a 2x2 grid. Casual stays the
|
||||
recommended option with a ★ marker.
|
||||
- Power-settings step covers Load Previous Session, Filter
|
||||
Include Previous Sessions, Auto-Tell-Tabs History Preload,
|
||||
Compact Density, Prettier Timestamps and a built-in theme
|
||||
picker. All six map to existing Configuration fields — no new
|
||||
settings introduced.
|
||||
- Staged commit: the wizard only writes to Config on the Finish
|
||||
step. Decide-later or X-close at any point leaves the existing
|
||||
config untouched.
|
||||
- Inline test hint on the done step: "type /tell <Player Name>
|
||||
into chat" surfaces the auto-tell-tab spawn mechanism.
|
||||
- Window starts at 720x480 (was 900x560) and can shrink to
|
||||
600x400; Step 1 keeps the fox banner in a folded TreeNode so
|
||||
the onboarding copy stays primary.
|
||||
- Existing users get the new wizard surfaced once on first boot
|
||||
after the update via the new WizardLastShownVersion config
|
||||
field. Future cycles bump the constant only when the wizard
|
||||
itself changes shape.
|
||||
---
|
||||
|
||||
**v1.5.5 — Upstream-Sync Tab-Features (2026-05-21)**
|
||||
|
||||
A backlog-sync cycle: inherited tab-feature items plus a new fox
|
||||
banner image and custom notification sounds.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Failed tells now raise a warning toast when a message you sent
|
||||
could not be delivered (recipient offline, in an instance, or
|
||||
blocking you). Toggle in Settings, Chat tab.
|
||||
- Per-tab notification sound: each tab can play a sound when a
|
||||
message arrives while you are looking at a different tab. Pick
|
||||
one of the 16 game chat sounds or one of three bundled Hellion
|
||||
sounds, with a preview button to hear it. Off by default,
|
||||
respects the global sound toggle.
|
||||
- The tab rename field in the right-click menu now focuses
|
||||
itself when the menu opens and accepts up to 512 characters,
|
||||
matching the settings-tab rename.
|
||||
- A jump-to-latest button appears in the chat log header while
|
||||
you are scrolled up from the live end.
|
||||
- Map flags and item links can be inserted into the chat input
|
||||
from its right-click menu.
|
||||
- The Hellion Forge fox banner in the first-run wizard and the
|
||||
Information tab is now a real image instead of ASCII art.
|
||||
|
||||
Schema bumped to v18 (additive fields only, no data migration).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.5.4 — Polish and Motion (2026-05-20)**
|
||||
|
||||
A polish cycle: smoother theme switching, faster theme and tab
|
||||
access, and subtle hover motion. Three P3 items plus an
|
||||
accessibility toggle.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Theme switches now crossfade smoothly over ~300 ms across every
|
||||
Hellion-rendered surface — sidebar, title, buttons, tabs,
|
||||
scrollbar, separators. The window background snaps deliberately
|
||||
so the per-window opacity override from Dalamud's pinning menu
|
||||
stays untouched.
|
||||
- New header quick-picker: a palette button left of the cog opens
|
||||
a compact popup with two sections — every built-in and custom
|
||||
theme, and every tab. The active entry carries a check glyph;
|
||||
clicking another switches without closing the popup.
|
||||
- Sidebar icons ease their opacity on hover, and card-mode message
|
||||
borders highlight per tab while the cursor is over their rows.
|
||||
Framerate-independent, so a stalled Wine frame cannot overshoot
|
||||
the animation.
|
||||
- New "Reduce motion" toggle in Theme & Layout disables the
|
||||
crossfade, the hover animations and the unread-tab pulse for
|
||||
users who prefer a static UI.
|
||||
|
||||
Under the hood:
|
||||
|
||||
- WizardStateSmokeStep added to /xlperf alongside the FontManager
|
||||
and ThemeSwitch self-tests.
|
||||
- Twelve new pure-helper xUnit Facts in the Build Suite cover
|
||||
all four privacy profile sets and their retention overrides.
|
||||
- Two pure-helper lerp paths (ThemeAbgrCacheLerp, FrameLerp) with
|
||||
xUnit coverage in the Build Suite, plus a ColourUtil.ApplyAlpha
|
||||
alpha modulator. Two new /xlperf self-test steps pin the
|
||||
crossfade and quick-picker contracts.
|
||||
|
||||
Migration v17 stays (no schema bump). The Configuration grows
|
||||
one optional string field (WizardLastShownVersion) which
|
||||
defaults to empty for legacy users.
|
||||
No schema bump, no migration. Migration v17 stays.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.5.1 — FontAtlas Refactor and Hellion Forge Signature (2026-05-17)**
|
||||
**v1.5.3 — Localisation Wave + Bundled-Font Overhaul (2026-05-19)**
|
||||
|
||||
Hybrid FontManager refactor plus an embedded provenance mark.
|
||||
|
||||
What changes under the hood:
|
||||
|
||||
- FontManager handle creation moves into the ctor inside a single
|
||||
atlas.SuppressAutoRebuild() block. The font atlas now builds once
|
||||
per plugin load instead of four to five times — less CPU and GPU
|
||||
pressure in the first seconds after a reload, less atlas texture
|
||||
memory churn.
|
||||
- Hybrid property model: Axis, AxisItalic and FontAwesome become
|
||||
init-only handles. RegularFont and ItalicFont stay mutable because
|
||||
the eight font settings still need to replace them at runtime —
|
||||
that path is funnelled through RebuildDelegateFonts() now and
|
||||
runs without a plugin reload.
|
||||
- FontAwesome reuses Dalamud's UiBuilder.IconFontFixedWidthHandle
|
||||
instead of building its own atlas slot. One delegate-build step
|
||||
less in the ctor.
|
||||
- BuildFontsAsync and BuildFonts are removed; the live mutation
|
||||
path is RebuildDelegateFonts() now.
|
||||
- Two FontManager self-test steps registered with /xlperf: ctor
|
||||
smoke (every handle non-null after Phase-1 resolve, no atlas
|
||||
load-exception) and push smoke (Push() returns without throwing).
|
||||
|
||||
Honorific full-gradient port (originally the v1.5.1 main item) was
|
||||
dropped: Honorific 3.2 exposes no IPC for the rendered gradient
|
||||
frame, and an in-plugin port of the colour palette was declined.
|
||||
The integration stays at the v1.4.7 glow-only shape.
|
||||
Multi-language pass plus a long-standing first-frame HITCH lands
|
||||
as a side effect of a font-stack rewrite.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Hellion Forge signature: a small fox-head ASCII silhouette is
|
||||
emitted to /xllog on every plugin load, and a full fox banner
|
||||
with "Hellion Forge" set inside the body is available as a
|
||||
folded TreeNode in the First-Run Wizard and Settings ->
|
||||
Information tab. Drawn by Julia Moon, embedded in the plugin DLL.
|
||||
- No settings changes, no migration. v17 stays.
|
||||
- 24 selectable UI languages (was 2). Catalan, Czech, Danish,
|
||||
Dutch, English, Finnish, French, German, Greek, Hungarian,
|
||||
Italian, Japanese, Korean, Norsk bokmål, Polish, Portuguese
|
||||
(BR + PT), Romanian, Russian, Spanish, Swedish, Turkish,
|
||||
Ukrainian, Simplified + Traditional Chinese. Sorted by endonym,
|
||||
"None" pinned first. Non-native locales are AI-assisted and
|
||||
flagged for native-speaker review via the Forge Discord.
|
||||
- Bundled Inter Light replaces Exo 2 (SIL OFL 1.1, 343 KB). The
|
||||
Inter font ships Latin Extended-A/B, Greek polytonic and
|
||||
Cyrillic Supplement coverage; NotoSansCjkRegular joins as a
|
||||
third merge layer for Hangul and Simplified-Han glyphs the
|
||||
FFXIV Japanese game font does not ship.
|
||||
- First-frame HITCH dropped from ~74 ms (v1.5.2 baseline that
|
||||
held since v1.4.x) to a median of ~20 ms (5-reload sample
|
||||
17.9-23.6 ms, Linux/Wine). The bundled-font path silently
|
||||
fell back to the FFXIV Axis font for the entire v1.5.x series
|
||||
because of an early-return in the draw loop. The fix that
|
||||
routes RegularFont through draw also lands the defer-pattern
|
||||
win the v1.5.1 cycle was reaching for.
|
||||
- ExtraGlyphRanges auto-activates on language change. Korean,
|
||||
ChineseFull and the two new flags (LatinExtended, Greek) toggle
|
||||
on without a manual visit to Fonts and Colours.
|
||||
- New WarningText under the language dropdown notes FFXIV's
|
||||
chat input only fully supports EN/DE/FR/JA character sets.
|
||||
Other languages render in HellionChat but may garble when
|
||||
typed into in-game chat.
|
||||
|
||||
Note on performance: the cross-plugin baseline target from v1.5.0
|
||||
(matching Lightless and XIVInstantMessenger at ~7 ms HITCH) did
|
||||
not land this cycle. HITCH stays around 80 ms because the cost is
|
||||
in the UiBuilder first-frame render path, not in the atlas build
|
||||
(which this cycle did reduce from 4-5 builds per load to 1). A
|
||||
first-frame render investigation is reserved for a later cycle.
|
||||
Under the hood:
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
- Three-layer font stack: Inter Light primary, FFXIV
|
||||
JapaneseFont merge 1 for kana/kanji style, NotoSansCjkRegular
|
||||
merge 2 for everything else CJK.
|
||||
- LanguageOverride enum gains ten locales plus three previously
|
||||
commented out (Italian, Korean, Norwegian as `nb`). New
|
||||
values append to the enum so existing config integers stay
|
||||
stable across update.
|
||||
- Crowdin gap closed: four post-sync ChatTwo keys backfilled
|
||||
into 13 legacy locales with per-key AI markers.
|
||||
- Plugin.LoadAsync runs a one-shot migration that ORs in the
|
||||
matching ExtraGlyphRanges flag for users already on a
|
||||
non-default language. Settings.Apply auto-activates on
|
||||
change going forward.
|
||||
- Em-dash sweep across the EN source and 18 translations to the
|
||||
house style. Russian and Ukrainian keep the typographic norm.
|
||||
|
||||
---
|
||||
|
||||
**v1.5.0 — DI Foundation and Service Refactor (2026-05-17)**
|
||||
|
||||
Major architecture cycle. The plugin bootstrap moves to a
|
||||
generic-host DI container (Microsoft.Extensions.Hosting +
|
||||
IServiceCollection) modelled on Lightless Sync. Service logging
|
||||
moves from a static Plugin.LogProxy locator to typed
|
||||
Microsoft.Extensions.Logging.ILogger<T> via constructor injection,
|
||||
bridged over Dalamud's IPluginLog by a custom DalamudLogger trio.
|
||||
|
||||
What changes under the hood:
|
||||
|
||||
- 18 instance-class services migrate to ILogger<T> via constructor
|
||||
injection across four slices: data layer (MessageStore,
|
||||
MessageManager, AutoTellTabsService), IPC and integrations
|
||||
(HonorificService, IpcManager, TypingIpc, ExtraChat, the three
|
||||
GameFunctions classes), UI window layer (ChatLogWindow,
|
||||
DbViewer, Popout, three settings tabs), and root (Commands,
|
||||
ThemeRegistry, PayloadHandler).
|
||||
- Plugin.LogProxy stays in place for the eight buckets ctor
|
||||
injection cannot reach: static helpers (EmoteCache,
|
||||
AutoTranslate, MemoryUtil, WrapperUtil), Dalamud-reflected
|
||||
types (Configuration), the Message data class, and instance
|
||||
classes that only log from static methods (FontManager, one
|
||||
GameFunctions site).
|
||||
- Plugin.cs finishes at 1012 lines — virtually identical to the
|
||||
pre-cycle 1013. The new Phase-1 host build and Plugin.X bridge
|
||||
wiring trade out exactly the service and window allocations
|
||||
that previously lived in LoadAsync.
|
||||
- Cross-plugin baseline confirms no performance penalty against
|
||||
Chat 2: HellionChat first-frame HITCH 77 ms median, Chat 2
|
||||
74 ms median. Lightless and XIVInstantMessenger sit around
|
||||
7 ms by deferring their font-atlas build past Finished
|
||||
loading — that pattern is the v1.5.1 follow-up.
|
||||
|
||||
User-visible:
|
||||
|
||||
- Slash-command insert fix: pasting a slash command into the
|
||||
chat input (Friend List "/tell" action, plugin-driven inserts
|
||||
from Artisan, AllaganTools etc.) now replaces the existing
|
||||
input instead of concatenating. Cherry-picked from ChatTwo
|
||||
upstream ee7768ac with namespace adaptation.
|
||||
|
||||
Migration v17 stays (no schema bump).
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
---
|
||||
|
||||
**v1.4.10 — Symbol-Picker and Tell-History Fix (2026-05-16)**
|
||||
|
||||
Eleventh and final sub-patch of the v1.4.x polish-sweep series.
|
||||
Symbol picker for the chat input, a tell-history reload fix for
|
||||
users with many active partners, and a closing cleanup sweep
|
||||
before v1.5.0 picks up the DI-container adoption.
|
||||
|
||||
- Symbol picker: a small smile-icon button left of the channel
|
||||
indicator opens a popup with two tabs. The first lists all 161
|
||||
FFXIV PUA glyphs (Dalamud's SeIconChar enum); the second
|
||||
carries 97 server-verified BMP symbols (latin marks, currency,
|
||||
the full Greek alphabet, geometric shapes, suits, notes) —
|
||||
every one of them round-tripped through /echo and /say in a
|
||||
four-round probe so the in-channel render matches what the
|
||||
picker shows. Click drops the glyph at the caret, multi-insert
|
||||
keeps the popup open, and a recent-used strip floats the last
|
||||
sixteen picks across both tabs. Toggle in Settings → Chat →
|
||||
Message behaviour, default on.
|
||||
- Pinned auto-tell tabs reload their full history again: a
|
||||
hidden 500-row scan cap in PreloadHistory used to override the
|
||||
user-configurable AutoTellTabsHistoryPreload setting, so
|
||||
less-frequent pinned partners (rare /tell sessions in an
|
||||
otherwise busy week) lost their backlog. The cap is removed;
|
||||
the (Receiver, Date) index keeps SQL fast, the client-side
|
||||
loop still respects your setting as the upper bound.
|
||||
- Slash-command teardown: /hellion, /hellionView,
|
||||
/hellionDebugger (and #if DEBUG /hellionSeString) wrappers are
|
||||
now cached as private fields. Plugin teardown detaches the
|
||||
live registration instead of re-Register'ing with identical
|
||||
args — closes a latent maintenance hazard from v1.4.9.
|
||||
- v1.4.x polish-sweep wraps up here. The ImGuiListClipper render
|
||||
refactor that was on the v1.4.10 reserve list got dropped
|
||||
after cross-platform smoke showed the scroll rubber-band is a
|
||||
Wine / Linux render-pipeline quirk, not universal — Windows
|
||||
users never saw it. It will get its own platform-targeted
|
||||
spike in a later patch. Next major cycle is v1.5.0 with the
|
||||
DI-container adoption (Microsoft.Extensions.Hosting +
|
||||
ILogger<T>) modelled on Lightless.
|
||||
- Migration v17 stays (no schema bump).
|
||||
Migration v17 stays. UseHellionFont users transition from Exo 2
|
||||
to Inter Light transparently on first reload.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dalamud.Plugin;
|
||||
using HellionChat.Integrations;
|
||||
using HellionChat.Ipc;
|
||||
using HellionChat.Themes;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
@@ -19,7 +20,7 @@ internal sealed class ThemeRegistryInitHostedService(ThemeRegistry registry) : I
|
||||
// warm cache; otherwise the first Switch falls through to the built-in
|
||||
// default when Config.Theme points at a custom slug.
|
||||
foreach (var _ in registry.AllCustom()) { }
|
||||
registry.Switch(Plugin.Config.Theme);
|
||||
registry.SwitchSilent(Plugin.Config.Theme);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -85,3 +86,18 @@ internal sealed class AutoTellTabsServiceInitHostedService(AutoTellTabsService s
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Eager-resolve trigger: resolving FailedTellNotifier in this adapter's ctor
|
||||
// enables its game hook during host startup. StartAsync itself is a no-op.
|
||||
internal sealed class FailedTellNotifierInitHostedService(FailedTellNotifier notifier)
|
||||
: IHostedService
|
||||
{
|
||||
// No-op adapter: the ctor dependency above is the actual eager-resolve
|
||||
// trigger. Field kept to match the IpcManager/TypingIpc/ExtraChat no-op
|
||||
// adapters and to avoid the CS9113 unread-parameter warning.
|
||||
private readonly FailedTellNotifier _notifier = notifier;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace HellionChat.Integrations;
|
||||
|
||||
// Plays the three bundled WAV notification sounds via NAudio WaveOutEvent.
|
||||
// WaveOutEvent/WinMM is the correct backend for FFXIV on Wine: it works
|
||||
// without Media Foundation (which Wine does not support for MP3/AAC).
|
||||
//
|
||||
// Playback volume comes from Configuration.CustomSoundVolume via the Play
|
||||
// parameter, clamped to [0,1]. The 16 game sounds are unaffected — they go
|
||||
// through UIGlobals.PlaySoundEffect, which the plugin cannot scale.
|
||||
internal sealed class CustomAudioPlayer : IDisposable
|
||||
{
|
||||
// Sound bytes are read once at construction so each Play() wraps a fresh
|
||||
// MemoryStream rather than re-reading the manifest stream (which becomes
|
||||
// unreadable after the first read and would require Seek support).
|
||||
private readonly byte[][] _soundData;
|
||||
private readonly ILogger<CustomAudioPlayer> _logger;
|
||||
|
||||
private WaveOutEvent? _outputDevice;
|
||||
private WaveFileReader? _reader;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public CustomAudioPlayer(ILogger<CustomAudioPlayer> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_soundData = new byte[3][];
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var resourceName = $"HellionChat.Sounds.notification-{i + 1}.wav";
|
||||
using var stream = typeof(CustomAudioPlayer).Assembly.GetManifestResourceStream(
|
||||
resourceName
|
||||
);
|
||||
if (stream is null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Embedded sound resource not found: {Resource}. "
|
||||
+ "Custom sound {Index} will be silent.",
|
||||
resourceName,
|
||||
i + 1
|
||||
);
|
||||
_soundData[i] = Array.Empty<byte>();
|
||||
continue;
|
||||
}
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
_soundData[i] = ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
// customIndex is 1, 2, or 3, matching the sound file suffix.
|
||||
// Stops any currently playing sound before starting the new one.
|
||||
// NAudio playback runs on its own thread; this method returns immediately.
|
||||
public void Play(int customIndex, float volume)
|
||||
{
|
||||
if (customIndex < 1 || customIndex > 3)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"CustomAudioPlayer.Play called with out-of-range index {Index}",
|
||||
customIndex
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = _soundData[customIndex - 1];
|
||||
if (data.Length == 0)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Sound data for index {Index} is empty; skipping playback",
|
||||
customIndex
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
try
|
||||
{
|
||||
StopCurrent();
|
||||
|
||||
var ms = new MemoryStream(data, writable: false);
|
||||
_reader = new WaveFileReader(ms);
|
||||
|
||||
_outputDevice = new WaveOutEvent();
|
||||
// Init opens the device and creates the WinMM handle. Volume
|
||||
// must be set after Init, otherwise waveOutSetVolume fails with
|
||||
// InvalidHandle.
|
||||
_outputDevice.Init(_reader);
|
||||
// AUDIO-1: volume comes from Configuration.CustomSoundVolume.
|
||||
// Clamp here too — a hand-edited config could carry an
|
||||
// out-of-range value, and WaveOutEvent.Volume rejects those.
|
||||
_outputDevice.Volume = Math.Clamp(volume, 0f, 1f);
|
||||
_outputDevice.Play();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
ex,
|
||||
"Failed to play custom notification sound {Index}",
|
||||
customIndex
|
||||
);
|
||||
StopCurrent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stops and tears down the active WaveOutEvent + WaveFileReader without
|
||||
// throwing. Called on Play (to interrupt previous sound) and from Dispose.
|
||||
// Guards Stop() with a PlaybackState check because waveOutReset blocks even
|
||||
// when playback already finished; under Wine this can stall the WinMM
|
||||
// callback thread if many sounds arrive in quick succession.
|
||||
private void StopCurrent()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_outputDevice?.PlaybackState == PlaybackState.Playing)
|
||||
_outputDevice.Stop();
|
||||
_outputDevice?.Dispose();
|
||||
_outputDevice = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Exception while stopping current WaveOutEvent");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_reader?.Dispose();
|
||||
_reader = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Exception while disposing WaveFileReader");
|
||||
}
|
||||
}
|
||||
|
||||
// At plugin unload the PendingMessageThread is already cancelled and the
|
||||
// draw loop is gone, so _lock is uncontended here. Calling StopCurrent
|
||||
// outside the lock avoids holding it across the blocking waveOutReset /
|
||||
// WaveOutEvent.Dispose, which can freeze on Wine during unload.
|
||||
public void Dispose()
|
||||
{
|
||||
StopCurrent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Integrations;
|
||||
|
||||
// A minimal, failed-tell-specific game hook. A locale-robust "tell failed"
|
||||
// signal is not reachable over the processed message stream (Message carries
|
||||
// no LogMessage row id, ChatCode 60 is too broad). This hooks the one
|
||||
// ShowLogMessageString overload and toasts on a pinned id set. It is NOT the
|
||||
// broad ad-block hook layer.
|
||||
internal sealed class FailedTellNotifier : IDisposable
|
||||
{
|
||||
private readonly ILogger<FailedTellNotifier> _logger;
|
||||
private readonly Hook<RaptureLogModule.Delegates.ShowLogMessageString>? _hook;
|
||||
|
||||
public unsafe FailedTellNotifier(ILogger<FailedTellNotifier> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
// Creating/enabling a hook is safe off the framework thread (the
|
||||
// ctor runs during host startup on the framework thread,
|
||||
// eager-resolved via FailedTellNotifierInitHostedService).
|
||||
_hook =
|
||||
Plugin.GameInteropProvider.HookFromAddress<RaptureLogModule.Delegates.ShowLogMessageString>(
|
||||
RaptureLogModule.MemberFunctionPointers.ShowLogMessageString,
|
||||
ShowLogMessageStringDetour
|
||||
);
|
||||
_hook.Enable();
|
||||
}
|
||||
|
||||
private unsafe void ShowLogMessageStringDetour(
|
||||
RaptureLogModule* module,
|
||||
uint logMessageId,
|
||||
Utf8String* value
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (
|
||||
FailedTellMatcher.ShouldNotify(
|
||||
logMessageId,
|
||||
Plugin.Config.NotifyFailedTell,
|
||||
FailedTellMatcher.FailedTellLogMessageIds
|
||||
)
|
||||
)
|
||||
{
|
||||
var recipient = value is null ? string.Empty : value->ToString();
|
||||
var content = string.IsNullOrEmpty(recipient)
|
||||
? HellionStrings.FailedTell_Notification_Generic
|
||||
: string.Format(HellionStrings.FailedTell_Notification_Named, recipient);
|
||||
WrapperUtil.AddNotification(content, NotificationType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "FailedTellNotifier detour threw");
|
||||
}
|
||||
|
||||
_hook!.Original(module, logMessageId, value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_hook?.Disable();
|
||||
_hook?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
@@ -330,6 +332,7 @@ internal class MessageManager : IAsyncDisposable
|
||||
Store.UpsertMessage(message);
|
||||
|
||||
var currentMatches = Plugin.CurrentTab.Matches(message);
|
||||
uint? notificationSound = null;
|
||||
foreach (var tab in Plugin.Config.Tabs)
|
||||
{
|
||||
var unread = !(
|
||||
@@ -337,7 +340,49 @@ internal class MessageManager : IAsyncDisposable
|
||||
);
|
||||
|
||||
if (tab.Matches(message))
|
||||
{
|
||||
tab.AddMessage(message, unread);
|
||||
|
||||
// Per-tab notification sound. Fire once for the first inactive
|
||||
// tab that wants it, keeping a message matching several
|
||||
// background tabs from stacking sounds.
|
||||
// TEST-MIRROR: ../_Helpers/TabSoundDecision.cs
|
||||
if (
|
||||
notificationSound is null
|
||||
&& TabSoundDecision.ShouldPlay(
|
||||
Plugin.CurrentTab == tab,
|
||||
tab.EnableNotificationSound,
|
||||
Plugin.Config.PlaySounds
|
||||
)
|
||||
)
|
||||
{
|
||||
notificationSound = tab.NotificationSoundId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (notificationSound is { } soundId)
|
||||
{
|
||||
if (soundId is >= 1 and <= 16)
|
||||
{
|
||||
// ProcessMessage runs on the PendingMessageThread worker; the native
|
||||
// UIGlobals.PlaySoundEffect must be marshalled onto the framework
|
||||
// thread (reference_dalamud_framework_thread).
|
||||
Plugin.Framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
UIGlobals.PlaySoundEffect(soundId);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (soundId >= 17)
|
||||
{
|
||||
// Custom bundled sounds (ids 17-19) go through NAudio WaveOutEvent.
|
||||
// NAudio manages its own playback thread, so no framework marshalling needed.
|
||||
Plugin.CustomAudioPlayer.Play((int)soundId - 16, Plugin.Config.CustomSoundVolume);
|
||||
}
|
||||
// soundId == 0 (hand-edited config) falls through: plays nothing.
|
||||
}
|
||||
|
||||
MessageProcessed?.Invoke(message);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using HellionChat.Resources;
|
||||
|
||||
namespace HellionChat;
|
||||
|
||||
// UI-7: how a sender's name is rendered in the chat log. Kept in its own file
|
||||
// (no Dalamud usings) so the SenderNameFormatter pure-helper test stays
|
||||
// AppDomain-isolated (feedback_dalamud_test_isolation).
|
||||
|
||||
public enum WorldSuffixMode
|
||||
{
|
||||
Never,
|
||||
OtherWorldOnly,
|
||||
Always,
|
||||
}
|
||||
|
||||
public enum NameFormMode
|
||||
{
|
||||
Full,
|
||||
FirstNameOnly,
|
||||
Initials,
|
||||
}
|
||||
|
||||
public static class NameDisplayModeExt
|
||||
{
|
||||
public static string Name(this WorldSuffixMode mode) =>
|
||||
mode switch
|
||||
{
|
||||
WorldSuffixMode.Never => HellionStrings.NameDisplay_WorldSuffix_Never,
|
||||
WorldSuffixMode.OtherWorldOnly => HellionStrings.NameDisplay_WorldSuffix_OtherWorldOnly,
|
||||
WorldSuffixMode.Always => HellionStrings.NameDisplay_WorldSuffix_Always,
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
|
||||
public static string Name(this NameFormMode mode) =>
|
||||
mode switch
|
||||
{
|
||||
NameFormMode.Full => HellionStrings.NameDisplay_NameForm_Full,
|
||||
NameFormMode.FirstNameOnly => HellionStrings.NameDisplay_NameForm_FirstNameOnly,
|
||||
NameFormMode.Initials => HellionStrings.NameDisplay_NameForm_Initials,
|
||||
_ => mode.ToString(),
|
||||
};
|
||||
}
|
||||
+30
-6
@@ -115,6 +115,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
internal Themes.ThemeRegistry ThemeRegistry { get; private set; } = null!;
|
||||
internal Ui.StatusBar StatusBar { get; private set; } = null!;
|
||||
internal Integrations.HonorificService HonorificService { get; private set; } = null!;
|
||||
internal Integrations.CustomAudioPlayer CustomAudioPlayer { get; private set; } = null!;
|
||||
|
||||
// Platform indirection over Dalamud.Utility.Util. Wired in Phase-1 ctor so
|
||||
// any service allocated in LoadAsync can read Plugin.PlatformUtil.
|
||||
@@ -198,10 +199,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// point (MigrateFromChatTwoLayout, LanguageChanged, ImGuiUtil.Initialize)
|
||||
// do not touch either static, so the brief null-window is safe.
|
||||
|
||||
// Schema gate: v1.4.x requires config v16+. Users on older schemas
|
||||
// must install v1.4.2 first to run the migration chain. v17 adds
|
||||
// Tab.IsPinned (additive, no data migration needed) so v16 configs
|
||||
// load cleanly and get their Version stamp bumped after the gate.
|
||||
// Schema gate: v1.4.x+ requires config v16+. Users on older schemas
|
||||
// must install v1.4.2 first to run the migration chain. v19 adds the
|
||||
// top-level CustomSoundVolume, WindowOpacityInactive, WorldSuffixMode
|
||||
// and NameFormMode fields — all additive with defaults, so v16-v18
|
||||
// configs load cleanly and get their Version stamp bumped after the
|
||||
// gate.
|
||||
if (Config.Version < 16)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
@@ -209,13 +212,24 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
+ "Please install v1.4.2 first to migrate the configuration, then upgrade to v1.4.10."
|
||||
);
|
||||
}
|
||||
Config.Version = 17;
|
||||
Config.Version = 19;
|
||||
|
||||
// Unpinned TempTabs are session-only and dropped on every load. Pinned
|
||||
// TempTabs survive reload — Jin's tester feedback (v1.4.7).
|
||||
Config.Tabs.RemoveAll(TabLifecycleHelpers.ShouldStripOnLoad);
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
|
||||
// v1.5.3 migration: Settings.Apply auto-activates the matching
|
||||
// ExtraGlyphRanges flag on a language CHANGE; a config that already
|
||||
// has e.g. Czech selected from a previous version never goes through
|
||||
// that path. ORing in the required flag here lets the first atlas
|
||||
// build pick it up, so an upgrade from v1.5.2 renders correctly
|
||||
// without forcing the user to toggle the language twice.
|
||||
var requiredRanges = Config.LanguageOverride.RequiredGlyphRanges();
|
||||
if (requiredRanges != 0 && !Config.ExtraGlyphRanges.HasFlag(requiredRanges))
|
||||
Config.ExtraGlyphRanges |= requiredRanges;
|
||||
|
||||
ImGuiUtil.Initialize(this);
|
||||
|
||||
DeferredSaveFrames = -1;
|
||||
@@ -273,6 +287,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
TypingIpc = _host.Services.GetRequiredService<TypingIpc>();
|
||||
ExtraChat = _host.Services.GetRequiredService<ExtraChat>();
|
||||
HonorificService = _host.Services.GetRequiredService<Integrations.HonorificService>();
|
||||
CustomAudioPlayer = _host.Services.GetRequiredService<Integrations.CustomAudioPlayer>();
|
||||
StatusBar = _host.Services.GetRequiredService<Ui.StatusBar>();
|
||||
MessageManager = _host.Services.GetRequiredService<MessageManager>();
|
||||
AutoTellTabsService = _host.Services.GetRequiredService<AutoTellTabsService>();
|
||||
@@ -319,9 +334,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
|
||||
SelfTestRegistry.RegisterTestSteps([
|
||||
new SelfTests.ThemeSwitchSelfTestStep(this),
|
||||
new SelfTests.ThemeCrossfadeSelfTestStep(this),
|
||||
new SelfTests.FontManagerCtorSmokeStep(this),
|
||||
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
|
||||
@@ -901,6 +919,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// Theme engine is always active; Classic is a theme, not a disabled state.
|
||||
using IDisposable _style = HellionStyle.PushGlobal(
|
||||
ThemeRegistry.Active,
|
||||
ThemeRegistry,
|
||||
Config.WindowOpacity
|
||||
);
|
||||
|
||||
@@ -934,7 +953,12 @@ public sealed class Plugin : IAsyncDalamudPlugin
|
||||
// RegularFont is nullable only because the live rebuild path
|
||||
// disposes it before reassigning; both ends of that swap happen on
|
||||
// this same draw thread, so it cannot be null here.
|
||||
using ((Config.FontsEnabled ? FontManager.RegularFont! : FontManager.Axis).Push())
|
||||
// v1.5.3 fix: also push RegularFont when the bundled Inter Light is
|
||||
// selected. Without this, UseHellionFont=true silently fell back to
|
||||
// the FFXIV Axis font because the Appearance tab forces FontsEnabled
|
||||
// off in that branch, and the bundled font never made it into draw.
|
||||
var useRegularFont = Config.FontsEnabled || Config.UseHellionFont;
|
||||
using ((useRegularFont ? FontManager.RegularFont! : FontManager.Axis).Push())
|
||||
WindowSystem.Draw();
|
||||
|
||||
ChatLogWindow.FinalizeFrame();
|
||||
|
||||
@@ -107,6 +107,12 @@ internal static class PluginHostFactory
|
||||
sp.GetRequiredService<ILogger<Integrations.HonorificService>>(),
|
||||
sp.GetRequiredService<IFramework>()
|
||||
));
|
||||
services.AddSingleton(sp => new Integrations.FailedTellNotifier(
|
||||
sp.GetRequiredService<ILogger<Integrations.FailedTellNotifier>>()
|
||||
));
|
||||
services.AddSingleton(sp => new Integrations.CustomAudioPlayer(
|
||||
sp.GetRequiredService<ILogger<Integrations.CustomAudioPlayer>>()
|
||||
));
|
||||
|
||||
services.AddSingleton(sp => new MessageManager(
|
||||
sp.GetRequiredService<Plugin>(),
|
||||
@@ -172,6 +178,11 @@ internal static class PluginHostFactory
|
||||
services.AddHostedService(sp => new AutoTellTabsServiceInitHostedService(
|
||||
sp.GetRequiredService<AutoTellTabsService>()
|
||||
));
|
||||
services.AddHostedService(
|
||||
sp => new Infrastructure.Hosting.FailedTellNotifierInitHostedService(
|
||||
sp.GetRequiredService<Integrations.FailedTellNotifier>()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 419 KiB |
@@ -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;
|
||||
Binary file not shown.
+103
-43
@@ -41,11 +41,9 @@ internal class HellionStrings
|
||||
private static string Get(string key)
|
||||
=> ResourceManager.GetString(key, resourceCulture) ?? key;
|
||||
|
||||
internal static string Privacy_Tab_Title => Get(nameof(Privacy_Tab_Title));
|
||||
internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name));
|
||||
internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description));
|
||||
internal static string Privacy_FilterEnabled_StorageOnly_Help => Get(nameof(Privacy_FilterEnabled_StorageOnly_Help));
|
||||
internal static string Privacy_Filter_Tree_Heading => Get(nameof(Privacy_Filter_Tree_Heading));
|
||||
internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help));
|
||||
internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst));
|
||||
internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll));
|
||||
@@ -260,8 +258,6 @@ internal class HellionStrings
|
||||
internal static string Settings_Card_Chat_Subtext => Get(nameof(Settings_Card_Chat_Subtext));
|
||||
internal static string Settings_Card_Tabs_Title => Get(nameof(Settings_Card_Tabs_Title));
|
||||
internal static string Settings_Card_Tabs_Subtext => Get(nameof(Settings_Card_Tabs_Subtext));
|
||||
internal static string Settings_Card_Privacy_Title => Get(nameof(Settings_Card_Privacy_Title));
|
||||
internal static string Settings_Card_Privacy_Subtext => Get(nameof(Settings_Card_Privacy_Subtext));
|
||||
internal static string Settings_Card_Database_Title => Get(nameof(Settings_Card_Database_Title));
|
||||
internal static string Settings_Card_Database_Subtext => Get(nameof(Settings_Card_Database_Subtext));
|
||||
internal static string Settings_Card_Information_Title => Get(nameof(Settings_Card_Information_Title));
|
||||
@@ -278,11 +274,7 @@ internal class HellionStrings
|
||||
internal static string Settings_Themes_ApplyChatColors_Apply => Get(nameof(Settings_Themes_ApplyChatColors_Apply));
|
||||
internal static string Settings_Themes_ApplyChatColors_Keep => Get(nameof(Settings_Themes_ApplyChatColors_Keep));
|
||||
|
||||
// Hellion Chat — General-Tab section headings
|
||||
internal static string Settings_General_Input_Heading => Get(nameof(Settings_General_Input_Heading));
|
||||
internal static string Settings_General_Audio_Heading => Get(nameof(Settings_General_Audio_Heading));
|
||||
internal static string Settings_General_Performance_Heading => Get(nameof(Settings_General_Performance_Heading));
|
||||
internal static string Settings_General_Language_Heading => Get(nameof(Settings_General_Language_Heading));
|
||||
internal static string Settings_Language_FFXIVCoverage_Warning => Get(nameof(Settings_Language_FFXIVCoverage_Warning));
|
||||
|
||||
// Hellion Chat — Appearance-Tab section headings
|
||||
internal static string Settings_Appearance_Theme_Heading => Get(nameof(Settings_Appearance_Theme_Heading));
|
||||
@@ -290,17 +282,8 @@ internal class HellionStrings
|
||||
internal static string Settings_Appearance_Colours_Heading => Get(nameof(Settings_Appearance_Colours_Heading));
|
||||
internal static string Settings_Appearance_Timestamps_Heading => Get(nameof(Settings_Appearance_Timestamps_Heading));
|
||||
|
||||
// Hellion Chat — Window-Tab section headings
|
||||
internal static string Settings_Window_Hide_Heading => Get(nameof(Settings_Window_Hide_Heading));
|
||||
internal static string Settings_Window_InactivityHide_Heading => Get(nameof(Settings_Window_InactivityHide_Heading));
|
||||
// Hellion Chat — Window-Tab section headings (pre-cycle legacy, kept for reference)
|
||||
internal static string Settings_Window_Frame_Heading => Get(nameof(Settings_Window_Frame_Heading));
|
||||
internal static string Settings_Window_Tooltips_Heading => Get(nameof(Settings_Window_Tooltips_Heading));
|
||||
|
||||
// Hellion Chat — Chat-Tab section headings
|
||||
internal static string Settings_Chat_AutoTellTabs_Heading => Get(nameof(Settings_Chat_AutoTellTabs_Heading));
|
||||
internal static string Settings_Chat_Behaviour_Heading => Get(nameof(Settings_Chat_Behaviour_Heading));
|
||||
internal static string Settings_Chat_Preview_Heading => Get(nameof(Settings_Chat_Preview_Heading));
|
||||
internal static string Settings_Chat_Emotes_Heading => Get(nameof(Settings_Chat_Emotes_Heading));
|
||||
|
||||
// Hellion Chat — Chat-Tab SymbolPicker
|
||||
internal static string Settings_Chat_SymbolPicker_Enable_Name => Get(nameof(Settings_Chat_SymbolPicker_Enable_Name));
|
||||
@@ -311,11 +294,6 @@ internal class HellionStrings
|
||||
internal static string Settings_Database_Viewer_Heading => Get(nameof(Settings_Database_Viewer_Heading));
|
||||
internal static string Settings_Database_Stats_Heading => Get(nameof(Settings_Database_Stats_Heading));
|
||||
|
||||
// Hellion Chat — Information-Tab section headings
|
||||
internal static string Settings_Information_VersionInfo_Heading => Get(nameof(Settings_Information_VersionInfo_Heading));
|
||||
internal static string Settings_Information_About_Heading => Get(nameof(Settings_Information_About_Heading));
|
||||
internal static string Settings_Information_Changelog_Heading => Get(nameof(Settings_Information_Changelog_Heading));
|
||||
|
||||
// Hellion Chat — Default tab presets (channel-themed)
|
||||
internal static string Tabs_Presets_System => Get(nameof(Tabs_Presets_System));
|
||||
internal static string Tabs_Presets_FreeCompany => Get(nameof(Tabs_Presets_FreeCompany));
|
||||
@@ -373,14 +351,8 @@ internal class HellionStrings
|
||||
internal static string Appearance_UseCompactDensity_Description => Get(nameof(Appearance_UseCompactDensity_Description));
|
||||
|
||||
// Hellion Chat — v1.2.1 Settings Cleanup: new card titles + subtexts
|
||||
internal static string Settings_Card_ThemeAndLayout_Title => Get(nameof(Settings_Card_ThemeAndLayout_Title));
|
||||
internal static string Settings_Card_ThemeAndLayout_Subtext => Get(nameof(Settings_Card_ThemeAndLayout_Subtext));
|
||||
internal static string Settings_Card_FontsAndColours_Title => Get(nameof(Settings_Card_FontsAndColours_Title));
|
||||
internal static string Settings_Card_FontsAndColours_Subtext => Get(nameof(Settings_Card_FontsAndColours_Subtext));
|
||||
internal static string Settings_Card_DataManagement_Title => Get(nameof(Settings_Card_DataManagement_Title));
|
||||
internal static string Settings_Card_DataManagement_Subtext => Get(nameof(Settings_Card_DataManagement_Subtext));
|
||||
internal static string Settings_Card_Integrations_Title => Get(nameof(Settings_Card_Integrations_Title));
|
||||
internal static string Settings_Card_Integrations_Subtext => Get(nameof(Settings_Card_Integrations_Subtext));
|
||||
|
||||
// Hellion Chat — v1.2.1 Theme & Layout tab section headings + WindowOpacity slider
|
||||
internal static string Settings_ThemeAndLayout_Theme_Heading => Get(nameof(Settings_ThemeAndLayout_Theme_Heading));
|
||||
@@ -389,26 +361,21 @@ internal class HellionStrings
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacity_Name => Get(nameof(Settings_ThemeAndLayout_WindowOpacity_Name));
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacity_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacity_Description));
|
||||
|
||||
// Hellion Chat — v1.2.1 Fonts & Colours tab section headings
|
||||
internal static string Settings_FontsAndColours_Fonts_Heading => Get(nameof(Settings_FontsAndColours_Fonts_Heading));
|
||||
internal static string Settings_FontsAndColours_Colours_Heading => Get(nameof(Settings_FontsAndColours_Colours_Heading));
|
||||
|
||||
// Hellion Chat — v1.2.1 Data Management tab section headings
|
||||
internal static string Settings_DataManagement_Storage_Heading => Get(nameof(Settings_DataManagement_Storage_Heading));
|
||||
internal static string Settings_DataManagement_Retention_Heading => Get(nameof(Settings_DataManagement_Retention_Heading));
|
||||
internal static string Settings_DataManagement_Cleanup_Heading => Get(nameof(Settings_DataManagement_Cleanup_Heading));
|
||||
internal static string Settings_DataManagement_Export_Heading => Get(nameof(Settings_DataManagement_Export_Heading));
|
||||
internal static string Settings_DataManagement_DbViewer_Heading => Get(nameof(Settings_DataManagement_DbViewer_Heading));
|
||||
internal static string Settings_DataManagement_Advanced_Heading => Get(nameof(Settings_DataManagement_Advanced_Heading));
|
||||
|
||||
// Hellion Chat — v1.2.1 Window-tab Behaviour heading (replaces Frame heading)
|
||||
internal static string Settings_Window_Frame_Behaviour_Heading => Get(nameof(Settings_Window_Frame_Behaviour_Heading));
|
||||
// v1.5.6: Data & Privacy tab section titles (R6)
|
||||
internal static string Settings_Section_PrivacyFilter => Get(nameof(Settings_Section_PrivacyFilter));
|
||||
internal static string Settings_Section_Storage => Get(nameof(Settings_Section_Storage));
|
||||
internal static string Settings_Section_Retention => Get(nameof(Settings_Section_Retention));
|
||||
internal static string Settings_Section_Cleanup => Get(nameof(Settings_Section_Cleanup));
|
||||
internal static string Settings_Section_Export => Get(nameof(Settings_Section_Export));
|
||||
internal static string Settings_Section_Database => Get(nameof(Settings_Section_Database));
|
||||
|
||||
// Hellion Chat — v1.2.1 Migration v15 → v16 toast
|
||||
internal static string Migration_v16_OverrideStyle_Toast => Get(nameof(Migration_v16_OverrideStyle_Toast));
|
||||
|
||||
// Hellion Chat — v1.3.0 Integrations tab (Honorific + Coming-Soon roadmap)
|
||||
internal static string Settings_Tab_Integrations => Get(nameof(Settings_Tab_Integrations));
|
||||
// Hellion Chat — v1.3.0 Integrations (Honorific + Coming-Soon roadmap) — now in About tab
|
||||
internal static string Settings_Integrations_Intro => Get(nameof(Settings_Integrations_Intro));
|
||||
internal static string Settings_Integrations_Honorific_SectionHeader => Get(nameof(Settings_Integrations_Honorific_SectionHeader));
|
||||
internal static string Settings_Integrations_Honorific_Status_Detected => Get(nameof(Settings_Integrations_Honorific_Status_Detected));
|
||||
@@ -443,4 +410,97 @@ internal class HellionStrings
|
||||
internal static string DbViewer_FullTextToggle => Get(nameof(DbViewer_FullTextToggle));
|
||||
internal static string DbViewer_FullTextToggle_Hint_Indexing => Get(nameof(DbViewer_FullTextToggle_Hint_Indexing));
|
||||
internal static string DbViewer_FullTextToggle_Hint_PhraseMode => Get(nameof(DbViewer_FullTextToggle_Hint_PhraseMode));
|
||||
|
||||
// Hellion Chat — v1.5.4 header quick-picker + reduce-motion toggle
|
||||
internal static string Settings_QuickPicker_Tooltip => Get(nameof(Settings_QuickPicker_Tooltip));
|
||||
internal static string Settings_QuickPicker_Themes_Header => Get(nameof(Settings_QuickPicker_Themes_Header));
|
||||
internal static string Settings_QuickPicker_Tabs_Header => Get(nameof(Settings_QuickPicker_Tabs_Header));
|
||||
internal static string Settings_ThemeAndLayout_ReduceMotion_Name => Get(nameof(Settings_ThemeAndLayout_ReduceMotion_Name));
|
||||
internal static string Settings_ThemeAndLayout_ReduceMotion_Description => Get(nameof(Settings_ThemeAndLayout_ReduceMotion_Description));
|
||||
|
||||
// Failed-tell notification
|
||||
internal static string FailedTell_Notification_Generic => Get(nameof(FailedTell_Notification_Generic));
|
||||
internal static string FailedTell_Notification_Named => Get(nameof(FailedTell_Notification_Named));
|
||||
internal static string Settings_Chat_NotifyFailedTell_Name => Get(nameof(Settings_Chat_NotifyFailedTell_Name));
|
||||
internal static string Settings_Chat_NotifyFailedTell_Description => Get(nameof(Settings_Chat_NotifyFailedTell_Description));
|
||||
|
||||
// Per-tab notification sound
|
||||
internal static string Tabs_NotificationSound_Enable_Name => Get(nameof(Tabs_NotificationSound_Enable_Name));
|
||||
internal static string Tabs_NotificationSound_Description => Get(nameof(Tabs_NotificationSound_Description));
|
||||
internal static string Tabs_NotificationSound_Option => Get(nameof(Tabs_NotificationSound_Option));
|
||||
internal static string Tabs_NotificationSound_Preview => Get(nameof(Tabs_NotificationSound_Preview));
|
||||
internal static string Tabs_NotificationSound_CustomOption => Get(nameof(Tabs_NotificationSound_CustomOption));
|
||||
|
||||
// Scroll-to-bottom and item/flag linking
|
||||
internal static string ChatLog_ScrollToBottom_Tooltip => Get(nameof(ChatLog_ScrollToBottom_Tooltip));
|
||||
internal static string ChatLog_Insert_MapFlag => Get(nameof(ChatLog_Insert_MapFlag));
|
||||
internal static string ChatLog_Insert_ItemLink => Get(nameof(ChatLog_Insert_ItemLink));
|
||||
|
||||
// v1.5.6: plugin-disclosure warning
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Name => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Name));
|
||||
internal static string Settings_Chat_NotifyPluginDisclosure_Description => Get(nameof(Settings_Chat_NotifyPluginDisclosure_Description));
|
||||
internal static string ChatInput_PluginDisclosure_Warning => Get(nameof(ChatInput_PluginDisclosure_Warning));
|
||||
|
||||
// v1.5.6: world suffix + name format display options
|
||||
internal static string Settings_Chat_WorldSuffix_Name => Get(nameof(Settings_Chat_WorldSuffix_Name));
|
||||
internal static string Settings_Chat_WorldSuffix_Description => Get(nameof(Settings_Chat_WorldSuffix_Description));
|
||||
internal static string Settings_Chat_NameForm_Name => Get(nameof(Settings_Chat_NameForm_Name));
|
||||
internal static string Settings_Chat_NameForm_Description => Get(nameof(Settings_Chat_NameForm_Description));
|
||||
internal static string NameDisplay_WorldSuffix_Never => Get(nameof(NameDisplay_WorldSuffix_Never));
|
||||
internal static string NameDisplay_WorldSuffix_OtherWorldOnly => Get(nameof(NameDisplay_WorldSuffix_OtherWorldOnly));
|
||||
internal static string NameDisplay_WorldSuffix_Always => Get(nameof(NameDisplay_WorldSuffix_Always));
|
||||
internal static string NameDisplay_NameForm_Full => Get(nameof(NameDisplay_NameForm_Full));
|
||||
internal static string NameDisplay_NameForm_FirstNameOnly => Get(nameof(NameDisplay_NameForm_FirstNameOnly));
|
||||
internal static string NameDisplay_NameForm_Initials => Get(nameof(NameDisplay_NameForm_Initials));
|
||||
|
||||
// v1.5.6: inactive window opacity
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Name => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Name));
|
||||
internal static string Settings_ThemeAndLayout_WindowOpacityInactive_Description => Get(nameof(Settings_ThemeAndLayout_WindowOpacityInactive_Description));
|
||||
|
||||
// v1.5.6: custom sound volume
|
||||
internal static string Settings_General_CustomSoundVolume_Name => Get(nameof(Settings_General_CustomSoundVolume_Name));
|
||||
internal static string Settings_General_CustomSoundVolume_Description => Get(nameof(Settings_General_CustomSoundVolume_Description));
|
||||
|
||||
// v1.5.6: General tab collapsible section titles (R6)
|
||||
internal static string Settings_Section_Input => Get(nameof(Settings_Section_Input));
|
||||
internal static string Settings_Section_Sound => Get(nameof(Settings_Section_Sound));
|
||||
internal static string Settings_Section_Language => Get(nameof(Settings_Section_Language));
|
||||
internal static string Settings_Section_Performance => Get(nameof(Settings_Section_Performance));
|
||||
internal static string Settings_Section_Sound_TabsHint => Get(nameof(Settings_Section_Sound_TabsHint));
|
||||
|
||||
// v1.5.6: Chat tab collapsible section titles (R6)
|
||||
internal static string Settings_Section_Messages => Get(nameof(Settings_Section_Messages));
|
||||
internal static string Settings_Section_InputPreview => Get(nameof(Settings_Section_InputPreview));
|
||||
internal static string Settings_Section_AutoTellTabs => Get(nameof(Settings_Section_AutoTellTabs));
|
||||
internal static string Settings_Section_Emotes => Get(nameof(Settings_Section_Emotes));
|
||||
internal static string Settings_Section_LinksTooltips => Get(nameof(Settings_Section_LinksTooltips));
|
||||
internal static string Settings_Section_NoviceNetwork => Get(nameof(Settings_Section_NoviceNetwork));
|
||||
|
||||
// v1.5.6: Appearance tab collapsible section titles (R6)
|
||||
internal static string Settings_Section_Theme => Get(nameof(Settings_Section_Theme));
|
||||
internal static string Settings_Section_Fonts => Get(nameof(Settings_Section_Fonts));
|
||||
internal static string Settings_Section_Colours => Get(nameof(Settings_Section_Colours));
|
||||
internal static string Settings_Section_WindowStyle => Get(nameof(Settings_Section_WindowStyle));
|
||||
internal static string Settings_Section_Timestamps => Get(nameof(Settings_Section_Timestamps));
|
||||
internal static string Settings_Section_Animations => Get(nameof(Settings_Section_Animations));
|
||||
|
||||
// v1.5.6: Window tab collapsible section titles (R6)
|
||||
internal static string Settings_Section_Hide => Get(nameof(Settings_Section_Hide));
|
||||
internal static string Settings_Section_InactivityHide => Get(nameof(Settings_Section_InactivityHide));
|
||||
internal static string Settings_Section_Frame => Get(nameof(Settings_Section_Frame));
|
||||
|
||||
// v1.5.6: Tabs tab per-tab-item sub-section titles (R6)
|
||||
internal static string Settings_Section_Tab_Channels => Get(nameof(Settings_Section_Tab_Channels));
|
||||
internal static string Settings_Section_Tab_Display => Get(nameof(Settings_Section_Tab_Display));
|
||||
internal static string Settings_Section_Tab_Notification => Get(nameof(Settings_Section_Tab_Notification));
|
||||
internal static string Settings_Section_Tab_Input => Get(nameof(Settings_Section_Tab_Input));
|
||||
internal static string Settings_Section_Tab_PopOut => Get(nameof(Settings_Section_Tab_PopOut));
|
||||
internal static string Settings_Section_Tab_Volume_AllTabsHint => Get(nameof(Settings_Section_Tab_Volume_AllTabsHint));
|
||||
|
||||
// v1.5.6: About tab collapsible section titles (R6)
|
||||
internal static string Settings_Section_Extensions => Get(nameof(Settings_Section_Extensions));
|
||||
internal static string Settings_Section_PluginInfo => Get(nameof(Settings_Section_PluginInfo));
|
||||
internal static string Settings_Section_Project => Get(nameof(Settings_Section_Project));
|
||||
internal static string Settings_Section_Translators => Get(nameof(Settings_Section_Translators));
|
||||
internal static string Settings_Section_Changelog => Get(nameof(Settings_Section_Changelog));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,6 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Privacy_Tab_Title" xml:space="preserve">
|
||||
<value>Datenschutz</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Name" xml:space="preserve">
|
||||
<value>Datenschutz-Filter aktivieren</value>
|
||||
</data>
|
||||
@@ -24,9 +21,6 @@
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>Der Filter steuert nur, was in die lokale Datenbank geschrieben wird. Im Chat-Log siehst du weiterhin jede Nachricht live, ausgeschlossene Kanäle werden nur nicht mehr gespeichert. Wenn du Kanäle auch aus der sichtbaren Anzeige entfernen willst, nutze die normalen Chat-Tab-Filter im Spiel.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy-Filter und Whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Wähle aus, welche Kanäle in die lokale Datenbank gespeichert werden. Standard nach Datensparsamkeit: nur deine eigenen Konversationen. Über die Buttons unten kannst du eine Voreinstellung anwenden.</value>
|
||||
</data>
|
||||
@@ -223,7 +217,7 @@
|
||||
<value>Wizard erneut zeigen</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Label" xml:space="preserve">
|
||||
<value>Später — Defaults behalten</value>
|
||||
<value>Später: Defaults behalten</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Tooltip" xml:space="preserve">
|
||||
<value>Schließt den Wizard ohne Profil-Auswahl. Die Plugin-Defaults bleiben aktiv und der Wizard erscheint beim nächsten Plugin-Reload erneut.</value>
|
||||
@@ -253,7 +247,7 @@
|
||||
<value>Roleplay</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Description" xml:space="preserve">
|
||||
<value>Wie Datensparsamkeit, plus Sagen und beide Emote-Typen für deine Story-Logs. Schreien und Rufen bleiben außen vor — Public-Distance-Lärm von Fremden ist kein Story-Inhalt. Aufbewahrung: Sagen 30 Tage, Emotes 90 Tage.</value>
|
||||
<value>Wie Datensparsamkeit, plus Sagen und beide Emote-Typen für deine Story-Logs. Schreien und Rufen bleiben außen vor. Public-Distance-Lärm von Fremden ist kein Story-Inhalt. Aufbewahrung: Sagen 30 Tage, Emotes 90 Tage.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Apply" xml:space="preserve">
|
||||
<value>Roleplay übernehmen</value>
|
||||
@@ -385,10 +379,10 @@
|
||||
<value>Wie deckend die Plugin-Fenster sind. Niedrigere Werte lassen das Spiel durchscheinen, Form-Felder und Dialoge bleiben oben drauf deckend und gut lesbar.</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Name" xml:space="preserve">
|
||||
<value>Mitgelieferte Hellion-Schrift (Exo 2) verwenden</value>
|
||||
<value>Mitgelieferte Inter Light verwenden</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Description" xml:space="preserve">
|
||||
<value>Rendert Chat und UI in Exo 2 (SIL Open Font License 1.1), die mit dem Plugin ausgeliefert wird. Deaktivieren, um auf die unter Einstellungen → Schrift gewählte Schriftart zurückzufallen.</value>
|
||||
<value>Stellt Chat und UI in Inter Light (SIL Open Font License 1.1) dar, die mit dem Plugin geliefert wird. Deaktivieren, um zur Schrift aus Einstellungen → Schriftart zurückzukehren.</value>
|
||||
</data>
|
||||
|
||||
<data name="About_Maintainer_Heading" xml:space="preserve">
|
||||
@@ -495,7 +489,7 @@
|
||||
<value>Maximal {0} angepinnte Tell-Tabs erreicht. Erst einen lösen oder dauerhaft behalten.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
||||
<value>Angepinnt — überlebt Relog.</value>
|
||||
<value>Angepinnt: überlebt Relog.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||
<value>Angepinnte Tabs überleben Relog und behalten die Bindung an die Tell-Person.</value>
|
||||
@@ -536,7 +530,7 @@
|
||||
<value>„Als begrüßt markieren"-Button anzeigen</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren — der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren: der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Neue /tell-Tabs direkt als Pop-Out öffnen</value>
|
||||
@@ -597,18 +591,6 @@
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Allgemein-Tabs -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Eingabe</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & Benachrichtigungen</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Sprache & Eingabe-Hilfen</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Aussehen-Tabs -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
@@ -625,32 +607,11 @@
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Fenster-Tabs -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inaktivitäts-Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Fenster-Rahmen</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Chat-Tabs -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Nachrichten-Verhalten</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Vorschau</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat-Tab SymbolPicker -->
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Name" xml:space="preserve">
|
||||
@@ -672,15 +633,6 @@
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Information-Tabs -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Versionsinfo</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>Über HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default-Tab-Presets (kanalspezifisch) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
@@ -811,12 +763,6 @@
|
||||
<data name="Settings_Card_Tabs_Subtext" xml:space="preserve">
|
||||
<value>Eigene Chat-Tabs anlegen und konfigurieren.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Title" xml:space="preserve">
|
||||
<value>Datenschutz</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Subtext" xml:space="preserve">
|
||||
<value>Privacy-Filter pro Channel und was gespeichert werden darf.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Title" xml:space="preserve">
|
||||
<value>Datenbank</value>
|
||||
</data>
|
||||
@@ -824,10 +770,10 @@
|
||||
<value>Speicher, Migration, alte Bereinigung</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Title" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
<value>Über</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Subtext" xml:space="preserve">
|
||||
<value>Version, Mission, Lizenz und Changelog.</value>
|
||||
<value>Erweiterungen, Version, Projektinformationen, Übersetzer und Changelog.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Themes" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
@@ -868,29 +814,11 @@
|
||||
<data name="Appearance_UseCompactDensity_Description" xml:space="preserve">
|
||||
<value>Schaltet das Message-Layout vom Card-Row-Default zurück auf einzeilige `[HH:mm] Sender: Text` Zeilen.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Title" xml:space="preserve">
|
||||
<value>Theme & Layout</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Subtext" xml:space="preserve">
|
||||
<value>Theme, Fenster-Rahmen und Zeitstempel-Style.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Title" xml:space="preserve">
|
||||
<value>Schriften & Farben</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Subtext" xml:space="preserve">
|
||||
<value>Schriftart, Schriftgröße und Chat-Farben pro Channel.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Title" xml:space="preserve">
|
||||
<value>Daten-Verwaltung</value>
|
||||
<value>Daten & Privatsphäre</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Subtext" xml:space="preserve">
|
||||
<value>Aufbewahrung, Aufräumen, Export und Datenbank-Statistiken.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Integrations_Title" xml:space="preserve">
|
||||
<value>Integrationen</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Integrations_Subtext" xml:space="preserve">
|
||||
<value>Andere Dalamud-Plugins, mit denen HellionChat zusammenarbeitet. Kommende Integrationen in der Vorschau.</value>
|
||||
<value>Privatsphäre-Filter, Aufbewahrung, Aufräumen, Export und Datenbank-Statistiken.</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
@@ -905,41 +833,14 @@
|
||||
<value>Fenster-Transparenz</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>Wie durchsichtig der Fensterhintergrund ist. Niedrigere Werte lassen mehr vom Spiel durchscheinen. Tipp: Dalamud's Per-Window-Menü (Hamburger in der Titelleiste) bietet pro Fenster eigene Overrides für Deckkraft, Hintergrund-Blur, Durchklick und Anpinnen — die haben Vorrang über diesen Slider für das jeweilige Fenster.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat-Farben</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Storage_Heading" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Retention_Heading" xml:space="preserve">
|
||||
<value>Aufbewahrung</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Cleanup_Heading" xml:space="preserve">
|
||||
<value>Cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Export_Heading" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_DbViewer_Heading" xml:space="preserve">
|
||||
<value>Datenbank-Viewer</value>
|
||||
<value>Wie durchsichtig der Fensterhintergrund ist. Niedrigere Werte lassen mehr vom Spiel durchscheinen. Tipp: Dalamud's Per-Window-Menü (Hamburger in der Titelleiste) bietet pro Fenster eigene Overrides für Deckkraft, Hintergrund-Blur, Durchklick und Anpinnen: die haben Vorrang über diesen Slider für das jeweilige Fenster.</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Advanced_Heading" xml:space="preserve">
|
||||
<value>Erweitert (Shift+Klick zum Öffnen)</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Verhalten</value>
|
||||
</data>
|
||||
<data name="Migration_v16_OverrideStyle_Toast" xml:space="preserve">
|
||||
<value>Hellion Chat 1.2.1 hat das Settings-Menü neu sortiert und die alte „Stilüberschreiben"-Option entfernt (überholt durch das Theme-System aus 1.1.0). Deine restlichen Einstellungen bleiben unverändert. Die Fenster-Transparenz ist nach „Theme & Layout" migriert. Ein Backup der vorherigen Config liegt unter pluginConfigs/HellionChat.json.pre-v16-backup neben der aktiven HellionChat.json.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Integrations" xml:space="preserve">
|
||||
<value>Integrationen</value>
|
||||
</data>
|
||||
<data name="Settings_Integrations_Intro" xml:space="preserve">
|
||||
<value>Plugin-Integrationen lassen HellionChat mit anderen installierten Dalamud-Plugins zusammenarbeiten. Jede Integration erkennt ihr Ziel automatisch und deaktiviert sich still, wenn das Ziel-Plugin fehlt.</value>
|
||||
</data>
|
||||
@@ -1030,4 +931,213 @@
|
||||
<data name="DbViewer_FullTextToggle_Hint_PhraseMode" xml:space="preserve">
|
||||
<value>Sucht nach der exakten Wortfolge. Mehrere Wörter werden nur gefunden, wenn sie zusammen und in dieser Reihenfolge stehen. Wer rohe FTS5-MATCH-Syntax nutzen will, setzt eigene Anführungszeichen um den Suchbegriff.</value>
|
||||
</data>
|
||||
<data name="Settings_Language_FFXIVCoverage_Warning" xml:space="preserve">
|
||||
<value>HellionChat zeigt alle 24 Sprachen, aber FFXIVs Chat-Eingabe unterstützt nur EN, DE, FR und JA vollständig. Andere Schriften können beim Tippen in den Spiel-Chat oder beim Senden von Nachrichten als unleserliche Zeichen erscheinen.</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Tooltip" xml:space="preserve">
|
||||
<value>Schnellauswahl für Themes und Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Themes_Header" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Tabs_Header" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_ReduceMotion_Name" xml:space="preserve">
|
||||
<value>Bewegung reduzieren</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_ReduceMotion_Description" xml:space="preserve">
|
||||
<value>Deaktiviert die Theme-Überblendung, die Hover-Animationen von Seitenleiste und Karten sowie das Pulsieren ungelesener Tabs. Theme-Wechsel und Hover-Zustände greifen dann sofort.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings Overhaul section titles (v1.5.6) -->
|
||||
<data name="Settings_Section_Input" xml:space="preserve">
|
||||
<value>Eingabe</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Sound" xml:space="preserve">
|
||||
<value>Audio</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Language" xml:space="preserve">
|
||||
<value>Sprache</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Performance" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Sound_TabsHint" xml:space="preserve">
|
||||
<value>Welcher Sound pro Tab abgespielt wird, wird im Kanäle-Tab eingestellt.</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Theme" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Fonts" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Colours" xml:space="preserve">
|
||||
<value>Farben</value>
|
||||
</data>
|
||||
<data name="Settings_Section_WindowStyle" xml:space="preserve">
|
||||
<value>Fenster-Stil</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Timestamps" xml:space="preserve">
|
||||
<value>Zeitstempel</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Animations" xml:space="preserve">
|
||||
<value>Animationen</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Messages" xml:space="preserve">
|
||||
<value>Nachrichten</value>
|
||||
</data>
|
||||
<data name="Settings_Section_InputPreview" xml:space="preserve">
|
||||
<value>Eingabe & Vorschau</value>
|
||||
</data>
|
||||
<data name="Settings_Section_AutoTellTabs" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Emotes" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
<data name="Settings_Section_LinksTooltips" xml:space="preserve">
|
||||
<value>Links & Tooltips</value>
|
||||
</data>
|
||||
<data name="Settings_Section_NoviceNetwork" xml:space="preserve">
|
||||
<value>Anfänger-Netzwerk</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Hide" xml:space="preserve">
|
||||
<value>Ausblenden</value>
|
||||
</data>
|
||||
<data name="Settings_Section_InactivityHide" xml:space="preserve">
|
||||
<value>Bei Inaktivität ausblenden</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Frame" xml:space="preserve">
|
||||
<value>Rahmen</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Channels" xml:space="preserve">
|
||||
<value>Kanäle</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Display" xml:space="preserve">
|
||||
<value>Anzeige</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Notification" xml:space="preserve">
|
||||
<value>Benachrichtigung</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Input" xml:space="preserve">
|
||||
<value>Eingabe</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_PopOut" xml:space="preserve">
|
||||
<value>Pop-Out-Fenster</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Volume_AllTabsHint" xml:space="preserve">
|
||||
<value>Diese Lautstärke gilt für alle Tabs.</value>
|
||||
</data>
|
||||
<data name="Settings_Section_PrivacyFilter" xml:space="preserve">
|
||||
<value>Datenschutz-Filter</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Storage" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Retention" xml:space="preserve">
|
||||
<value>Aufbewahrung</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Cleanup" xml:space="preserve">
|
||||
<value>Aufräumen</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Export" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Database" xml:space="preserve">
|
||||
<value>Datenbank</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Extensions" xml:space="preserve">
|
||||
<value>Erweiterungen</value>
|
||||
</data>
|
||||
<data name="Settings_Section_PluginInfo" xml:space="preserve">
|
||||
<value>Plugin-Info</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Project" xml:space="preserve">
|
||||
<value>Das Projekt</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Translators" xml:space="preserve">
|
||||
<value>Übersetzer</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Changelog" xml:space="preserve">
|
||||
<value>Änderungsprotokoll</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyFailedTell_Name" xml:space="preserve">
|
||||
<value>Benachrichtigung bei fehlgeschlagenem Tell</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyFailedTell_Description" xml:space="preserve">
|
||||
<value>Zeigt eine Toast-Meldung an, wenn ein von dir gesendeter Tell nicht zugestellt werden konnte (Empfänger offline, in einer Instanz oder hat dich blockiert).</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Name" xml:space="preserve">
|
||||
<value>Warnung vor dem Senden plugin-exklusiver Symbole</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Description" xml:space="preserve">
|
||||
<value>Zeigt eine Warnung an, wenn eine Nachricht plugin-exklusive Symbole enthält, die für Spieler ohne HellionChat oder ein ähnliches Plugin als leere Kästchen erscheinen.</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_WorldSuffix_Name" xml:space="preserve">
|
||||
<value>Welt-Suffix</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_WorldSuffix_Description" xml:space="preserve">
|
||||
<value>Wann der Heimatwelt-Name an den Absendernamen im Chat-Protokoll angehängt wird.</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Name" xml:space="preserve">
|
||||
<value>Namensformat</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Description" xml:space="preserve">
|
||||
<value>Wie Absendernamen im Chat-Protokoll angezeigt werden. Der vollständige Name ist der Standard.</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Never" xml:space="preserve">
|
||||
<value>Nie</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_OtherWorldOnly" xml:space="preserve">
|
||||
<value>Nur andere Welten</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Always" xml:space="preserve">
|
||||
<value>Immer</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Full" xml:space="preserve">
|
||||
<value>Vollständiger Name</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_FirstNameOnly" xml:space="preserve">
|
||||
<value>Nur Vorname</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Initials" xml:space="preserve">
|
||||
<value>Initialen</value>
|
||||
</data>
|
||||
<data name="Settings_General_CustomSoundVolume_Name" xml:space="preserve">
|
||||
<value>Eigene Lautstärke</value>
|
||||
</data>
|
||||
<data name="Settings_General_CustomSoundVolume_Description" xml:space="preserve">
|
||||
<value>Wiedergabelautstärke für die drei mitgelieferten eigenen Benachrichtigungstöne. Beeinflusst nicht die 16 Spielsounds.</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Name" xml:space="preserve">
|
||||
<value>Inaktive Fenster-Deckkraft</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Description" xml:space="preserve">
|
||||
<value>Hintergrund-Deckkraft des Haupt-Chat-Fensters, wenn es nicht im Fokus ist. Der Regler darüber legt den Wert im Fokus fest. Eine fensterbasierte Überschreibung im Dalamud-Pinning-Menü hat Vorrang vor beiden Werten.</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Enable_Name" xml:space="preserve">
|
||||
<value>Benachrichtigungston</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Description" xml:space="preserve">
|
||||
<value>Spielt einen Ton ab, wenn eine Nachricht in diesem Tab eintrifft, während du einen anderen Tab anschaust. Respektiert den globalen Sound-Schalter.</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Option" xml:space="preserve">
|
||||
<value>Sound</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Preview" xml:space="preserve">
|
||||
<value>Vorschau des ausgewählten Sounds</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_CustomOption" xml:space="preserve">
|
||||
<value>Hellion-Sound</value>
|
||||
</data>
|
||||
<data name="FailedTell_Notification_Generic" xml:space="preserve">
|
||||
<value>Ein Tell konnte nicht zugestellt werden.</value>
|
||||
</data>
|
||||
<data name="FailedTell_Notification_Named" xml:space="preserve">
|
||||
<value>Tell an {0} konnte nicht zugestellt werden.</value>
|
||||
</data>
|
||||
<data name="ChatInput_PluginDisclosure_Warning" xml:space="preserve">
|
||||
<value>Diese Nachricht enthält plugin-exklusive Symbole, die andere Spieler als leere Kästchen sehen könnten. Drücke Enter erneut, um sie trotzdem zu senden.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,21 +12,15 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Privacy_Tab_Title" xml:space="preserve">
|
||||
<value>Privacy</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Name" xml:space="preserve">
|
||||
<value>Enable privacy filter</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_Description" xml:space="preserve">
|
||||
<value>When enabled, only messages from allowed channels are written to the database. When disabled, the default behaviour applies — everything except battle logs is stored.</value>
|
||||
<value>When enabled, only messages from allowed channels are written to the database. When disabled, the default behaviour applies. Everything except battle logs is stored.</value>
|
||||
</data>
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>The filter only controls what is written to the local database. The chat log still shows every message live; excluded channels are simply no longer stored. If you also want to remove channels from the visible display, use the normal chat-tab filters in the game.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy filter and whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Choose which channels are saved to the local database. Default follows data minimisation: only your own conversations. Use the buttons below to apply a preset.</value>
|
||||
</data>
|
||||
@@ -82,7 +76,7 @@
|
||||
<value>The manual run uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current changes.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Preview is stale — your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
<value>Preview is stale: your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Refresh preview</value>
|
||||
@@ -133,7 +127,7 @@
|
||||
<value>Automatically delete messages past their channel retention window</value>
|
||||
</data>
|
||||
<data name="Retention_Enabled_Description" xml:space="preserve">
|
||||
<value>When enabled, messages older than the configured window are deleted on each plugin start (at most once every 24 hours). Default is OFF — the plugin never deletes anything without your explicit consent.</value>
|
||||
<value>When enabled, messages older than the configured window are deleted on each plugin start (at most once every 24 hours). Default is OFF. The plugin never deletes anything without your explicit consent.</value>
|
||||
</data>
|
||||
<data name="Retention_Default_Label" xml:space="preserve">
|
||||
<value>Default retention (days, 0 = never)</value>
|
||||
@@ -223,7 +217,7 @@
|
||||
<value>Show wizard again</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Label" xml:space="preserve">
|
||||
<value>Later — keep defaults</value>
|
||||
<value>Later: keep defaults</value>
|
||||
</data>
|
||||
<data name="Wizard_Cancel_Tooltip" xml:space="preserve">
|
||||
<value>Close the wizard without selecting a profile. The plugin defaults stay active and the wizard returns on next plugin load.</value>
|
||||
@@ -253,7 +247,7 @@
|
||||
<value>Roleplay</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Description" xml:space="preserve">
|
||||
<value>Like Privacy First, plus Say and both emote types for your story logs. Shout and Yell stay out — public-distance noise from strangers is not story content. Retention: Say 30 days, emotes 90 days.</value>
|
||||
<value>Like Privacy First, plus Say and both emote types for your story logs. Shout and Yell stay out. Public-distance noise from strangers is not story content. Retention: Say 30 days, emotes 90 days.</value>
|
||||
</data>
|
||||
<data name="Wizard_Profile_Roleplay_Apply" xml:space="preserve">
|
||||
<value>Apply roleplay</value>
|
||||
@@ -385,10 +379,10 @@
|
||||
<value>How opaque the plugin windows are. Lower values let the game show through; form fields and dialogs stay fully opaque and readable on top.</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Name" xml:space="preserve">
|
||||
<value>Use bundled Hellion font (Exo 2)</value>
|
||||
<value>Use bundled Inter Light</value>
|
||||
</data>
|
||||
<data name="Theme_UseHellionFont_Description" xml:space="preserve">
|
||||
<value>Renders chat and UI in Exo 2 (SIL Open Font License 1.1), which ships with the plugin. Disable to fall back to the font selected under Settings → Font.</value>
|
||||
<value>Renders chat and UI in Inter Light (SIL Open Font License 1.1), which ships with the plugin. Disable to fall back to the font selected under Settings → Font.</value>
|
||||
</data>
|
||||
|
||||
<data name="About_Maintainer_Heading" xml:space="preserve">
|
||||
@@ -421,7 +415,7 @@
|
||||
<value>Hellion Chat is a fork of Chat 2 by Infi and Anna (ascclemens). The chat-replacement window, IPC integration, render engine, and the entire storage core all come from the original.</value>
|
||||
</data>
|
||||
<data name="About_BuiltOn_P2" xml:space="preserve">
|
||||
<value>The web interface is the only major piece I removed. It is built for remote access to the chat from a second device — a different focus from the smaller default footprint this fork pursues. Adapting it to these defaults would have required significant rework, so removing it was the clean path for this particular fork.</value>
|
||||
<value>The web interface is the only major piece I removed. It is built for remote access to the chat from a second device: a different focus from the smaller default footprint this fork pursues. Adapting it to these defaults would have required significant rework, so removing it was the clean path for this particular fork.</value>
|
||||
</data>
|
||||
<data name="About_BuiltOn_Upstream_Label" xml:space="preserve">
|
||||
<value>Upstream repository:</value>
|
||||
@@ -489,7 +483,7 @@
|
||||
<value>Promote to permanent</value>
|
||||
</data>
|
||||
<data name="PinTab_PromoteTooltip" xml:space="preserve">
|
||||
<value>Turns this TempTell into a regular tab. The tell binding to the partner is dropped — the tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead.</value>
|
||||
<value>Turns this TempTell into a regular tab. The tell binding to the partner is dropped. The tab will catch messages by its channel filters from now on. For "tab survives relog while staying bound to this partner", use Pin Tab instead.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinTooltip" xml:space="preserve">
|
||||
<value>Pinned tabs survive relog and stay bound to this conversation partner.</value>
|
||||
@@ -507,7 +501,7 @@
|
||||
<value>Maximum of {0} pinned tell tabs reached. Unpin one first, or use Promote to permanent.</value>
|
||||
</data>
|
||||
<data name="PinTab_PinnedTooltip" xml:space="preserve">
|
||||
<value>Pinned — survives relog.</value>
|
||||
<value>Pinned: survives relog.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat settings tab) -->
|
||||
@@ -536,7 +530,7 @@
|
||||
<value>Show "Mark as greeted" button</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Adds a click button next to each auto-tell tab to mark a conversation partner as already greeted — the tab name is then dimmed. Useful for club greeters managing many conversations in parallel. Off by default.</value>
|
||||
<value>Adds a click button next to each auto-tell tab to mark a conversation partner as already greeted. The tab name is then dimmed. Useful for club greeters managing many conversations in parallel. Off by default.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_OpenAsPopout_Name" xml:space="preserve">
|
||||
<value>Open new /tell tabs directly as pop-outs</value>
|
||||
@@ -596,20 +590,6 @@
|
||||
<value>About</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — General tab section headings -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & notifications</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Language & input aids</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Appearance tab section headings -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
@@ -624,34 +604,10 @@
|
||||
<value>Timestamps</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Window tab section headings -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Hiding</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inactivity hiding</value>
|
||||
</data>
|
||||
<!-- Hellion Chat — Window tab section headings (pre-cycle legacy, kept for reference) -->
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Window frame</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat tab section headings -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Message behaviour</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat tab SymbolPicker -->
|
||||
<data name="Settings_Chat_SymbolPicker_Enable_Name" xml:space="preserve">
|
||||
<value>Show symbol-picker button next to chat input</value>
|
||||
@@ -671,17 +627,6 @@
|
||||
<value>Maintenance</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Information tab section headings -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Version info</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>About HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default tab presets (channel-specific) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
@@ -811,12 +756,6 @@
|
||||
<data name="Settings_Card_Tabs_Subtext" xml:space="preserve">
|
||||
<value>Create and configure custom chat tabs.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Title" xml:space="preserve">
|
||||
<value>Privacy</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Privacy_Subtext" xml:space="preserve">
|
||||
<value>Privacy filter per channel and what may be stored.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Database_Title" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
@@ -824,10 +763,10 @@
|
||||
<value>Storage, migration, legacy cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Title" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Information_Subtext" xml:space="preserve">
|
||||
<value>Version, mission, licence, and changelog.</value>
|
||||
<value>Extensions, version, project info, translators, and changelog.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Themes" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
@@ -868,29 +807,11 @@
|
||||
<data name="Appearance_UseCompactDensity_Description" xml:space="preserve">
|
||||
<value>Switches the message layout from the card-row default back to single-line `[HH:mm] Sender: Text` rows.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Title" xml:space="preserve">
|
||||
<value>Theme & Layout</value>
|
||||
</data>
|
||||
<data name="Settings_Card_ThemeAndLayout_Subtext" xml:space="preserve">
|
||||
<value>Theme, window frame, and timestamp style.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Title" xml:space="preserve">
|
||||
<value>Fonts & Colours</value>
|
||||
</data>
|
||||
<data name="Settings_Card_FontsAndColours_Subtext" xml:space="preserve">
|
||||
<value>Font, font size, and chat colours per channel.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Title" xml:space="preserve">
|
||||
<value>Data management</value>
|
||||
<value>Data & Privacy</value>
|
||||
</data>
|
||||
<data name="Settings_Card_DataManagement_Subtext" xml:space="preserve">
|
||||
<value>Retention, cleanup, export, and database statistics.</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Integrations_Title" xml:space="preserve">
|
||||
<value>Integrations</value>
|
||||
</data>
|
||||
<data name="Settings_Card_Integrations_Subtext" xml:space="preserve">
|
||||
<value>Other Dalamud plugins that HellionChat works with. Upcoming integrations in preview.</value>
|
||||
<value>Privacy filter, retention, cleanup, export, and database statistics.</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
@@ -905,41 +826,32 @@
|
||||
<value>Window transparency</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacity_Description" xml:space="preserve">
|
||||
<value>How transparent the window background is. Lower values let more of the game show through. Tip: Dalamud's per-window menu (hamburger in the title bar) offers per-window overrides for opacity, background blur, click-through, and pinning — those take precedence over this slider for the respective window.</value>
|
||||
<value>How transparent the window background is. Lower values let more of the game show through. Tip: Dalamud's per-window menu (hamburger in the title bar) offers per-window overrides for opacity, background blur, click-through, and pinning. Those take precedence over this slider for the respective window.</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Fonts_Heading" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
<data name="Settings_Section_PrivacyFilter" xml:space="preserve">
|
||||
<value>Privacy filter</value>
|
||||
</data>
|
||||
<data name="Settings_FontsAndColours_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat colours</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Storage_Heading" xml:space="preserve">
|
||||
<data name="Settings_Section_Storage" xml:space="preserve">
|
||||
<value>Storage</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Retention_Heading" xml:space="preserve">
|
||||
<data name="Settings_Section_Retention" xml:space="preserve">
|
||||
<value>Retention</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Cleanup_Heading" xml:space="preserve">
|
||||
<data name="Settings_Section_Cleanup" xml:space="preserve">
|
||||
<value>Cleanup</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Export_Heading" xml:space="preserve">
|
||||
<data name="Settings_Section_Export" xml:space="preserve">
|
||||
<value>Export</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_DbViewer_Heading" xml:space="preserve">
|
||||
<value>Database viewer</value>
|
||||
<data name="Settings_Section_Database" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
<data name="Settings_DataManagement_Advanced_Heading" xml:space="preserve">
|
||||
<value>Advanced (Shift+click to open)</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Behaviour</value>
|
||||
</data>
|
||||
<data name="Migration_v16_OverrideStyle_Toast" xml:space="preserve">
|
||||
<value>Hellion Chat 1.2.1 has reorganised the settings menu and removed the old "Override style" option (superseded by the theme system from 1.1.0). Your remaining settings are unchanged. Window transparency has been migrated to "Theme & Layout". A backup of the previous config is located at pluginConfigs/HellionChat.json.pre-v16-backup next to the active HellionChat.json.</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Integrations" xml:space="preserve">
|
||||
<value>Integrations</value>
|
||||
</data>
|
||||
<data name="Settings_Integrations_Intro" xml:space="preserve">
|
||||
<value>Plugin integrations let HellionChat work together with other installed Dalamud plugins. Each integration automatically detects its target and silently disables itself when the target plugin is missing.</value>
|
||||
</data>
|
||||
@@ -1030,4 +942,228 @@
|
||||
<data name="DbViewer_FullTextToggle_Hint_PhraseMode" xml:space="preserve">
|
||||
<value>Searches for the exact phrase. Multi-word queries match only when the words appear together in order. To use raw FTS5 MATCH syntax, wrap your term in double quotes yourself.</value>
|
||||
</data>
|
||||
<data name="Settings_Language_FFXIVCoverage_Warning" xml:space="preserve">
|
||||
<value>HellionChat renders all 24 languages, but FFXIV's chat input only fully supports EN, DE, FR and JA. Other scripts may display as garbled characters when typed into the in-game chat or sent as messages.</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Tooltip" xml:space="preserve">
|
||||
<value>Quick picker for themes and tabs</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Themes_Header" xml:space="preserve">
|
||||
<value>Themes</value>
|
||||
</data>
|
||||
<data name="Settings_QuickPicker_Tabs_Header" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_ReduceMotion_Name" xml:space="preserve">
|
||||
<value>Reduce motion</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_ReduceMotion_Description" xml:space="preserve">
|
||||
<value>Disables the theme crossfade, the sidebar and card-row hover animations, and the unread-tab pulse. Theme switches and hover states apply instantly instead.</value>
|
||||
</data>
|
||||
|
||||
<!-- Failed-tell notification -->
|
||||
<data name="FailedTell_Notification_Generic" xml:space="preserve">
|
||||
<value>A tell could not be delivered.</value>
|
||||
</data>
|
||||
<data name="FailedTell_Notification_Named" xml:space="preserve">
|
||||
<value>Tell to {0} could not be delivered.</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyFailedTell_Name" xml:space="preserve">
|
||||
<value>Notify on failed tell</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyFailedTell_Description" xml:space="preserve">
|
||||
<value>Show a toast when a tell you sent could not be delivered (recipient offline, in an instance, or blocking you).</value>
|
||||
</data>
|
||||
|
||||
<!-- Per-tab notification sound -->
|
||||
<data name="Tabs_NotificationSound_Enable_Name" xml:space="preserve">
|
||||
<value>Notification sound</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Description" xml:space="preserve">
|
||||
<value>Play a sound when a message arrives in this tab while you are looking at a different tab. Respects the global sound toggle.</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Option" xml:space="preserve">
|
||||
<value>Sound</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_Preview" xml:space="preserve">
|
||||
<value>Preview the selected sound</value>
|
||||
</data>
|
||||
<data name="Tabs_NotificationSound_CustomOption" xml:space="preserve">
|
||||
<value>Hellion sound</value>
|
||||
</data>
|
||||
|
||||
<!-- Scroll-to-bottom and item/flag linking -->
|
||||
<data name="ChatLog_ScrollToBottom_Tooltip" xml:space="preserve">
|
||||
<value>Jump to the latest message</value>
|
||||
</data>
|
||||
<data name="ChatLog_Insert_MapFlag" xml:space="preserve">
|
||||
<value>Insert map flag <flag></value>
|
||||
</data>
|
||||
<data name="ChatLog_Insert_ItemLink" xml:space="preserve">
|
||||
<value>Insert linked item <item></value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: plugin-disclosure warning -->
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Name" xml:space="preserve">
|
||||
<value>Warn before sending plugin-only symbols</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NotifyPluginDisclosure_Description" xml:space="preserve">
|
||||
<value>Show a warning when a message you are about to send contains symbols that only display correctly for players running HellionChat or a similar plugin.</value>
|
||||
</data>
|
||||
<data name="ChatInput_PluginDisclosure_Warning" xml:space="preserve">
|
||||
<value>This message contains plugin-only symbols that other players may see as empty boxes. Press Enter again to send anyway.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: world suffix + name format display options -->
|
||||
<data name="Settings_Chat_WorldSuffix_Name" xml:space="preserve">
|
||||
<value>World suffix</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_WorldSuffix_Description" xml:space="preserve">
|
||||
<value>When to append the home world to a sender's name in the chat log.</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Name" xml:space="preserve">
|
||||
<value>Name format</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_NameForm_Description" xml:space="preserve">
|
||||
<value>How sender names are shown in the chat log. The full name is the default.</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Never" xml:space="preserve">
|
||||
<value>Never</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_OtherWorldOnly" xml:space="preserve">
|
||||
<value>Other worlds only</value>
|
||||
</data>
|
||||
<data name="NameDisplay_WorldSuffix_Always" xml:space="preserve">
|
||||
<value>Always</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Full" xml:space="preserve">
|
||||
<value>Full name</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_FirstNameOnly" xml:space="preserve">
|
||||
<value>First name only</value>
|
||||
</data>
|
||||
<data name="NameDisplay_NameForm_Initials" xml:space="preserve">
|
||||
<value>Initials</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: inactive window opacity -->
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Name" xml:space="preserve">
|
||||
<value>Inactive window opacity</value>
|
||||
</data>
|
||||
<data name="Settings_ThemeAndLayout_WindowOpacityInactive_Description" xml:space="preserve">
|
||||
<value>Background opacity of the main chat window while it is not focused. The slider above sets the focused value. A per-window override in Dalamud's window pinning menu still takes precedence over both.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: custom sound volume -->
|
||||
<data name="Settings_General_CustomSoundVolume_Name" xml:space="preserve">
|
||||
<value>Custom sound volume</value>
|
||||
</data>
|
||||
<data name="Settings_General_CustomSoundVolume_Description" xml:space="preserve">
|
||||
<value>Playback volume for the three bundled custom notification sounds. Does not affect the 16 game sounds.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: General tab section titles (collapsible, R6) -->
|
||||
<data name="Settings_Section_Input" xml:space="preserve">
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Sound" xml:space="preserve">
|
||||
<value>Sound</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Language" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Performance" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Sound_TabsHint" xml:space="preserve">
|
||||
<value>Which sound plays per tab is set in the Channels tab.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: Chat tab section titles (collapsible, R6) -->
|
||||
<data name="Settings_Section_Messages" xml:space="preserve">
|
||||
<value>Messages</value>
|
||||
</data>
|
||||
<data name="Settings_Section_InputPreview" xml:space="preserve">
|
||||
<value>Input & preview</value>
|
||||
</data>
|
||||
<data name="Settings_Section_AutoTellTabs" xml:space="preserve">
|
||||
<value>Auto-tell tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Emotes" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
<data name="Settings_Section_LinksTooltips" xml:space="preserve">
|
||||
<value>Links & tooltips</value>
|
||||
</data>
|
||||
<data name="Settings_Section_NoviceNetwork" xml:space="preserve">
|
||||
<value>Novice network</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: Appearance tab section titles (collapsible, R6) -->
|
||||
<data name="Settings_Section_Theme" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Fonts" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Colours" xml:space="preserve">
|
||||
<value>Colours</value>
|
||||
</data>
|
||||
<data name="Settings_Section_WindowStyle" xml:space="preserve">
|
||||
<value>Window style</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Timestamps" xml:space="preserve">
|
||||
<value>Timestamps</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Animations" xml:space="preserve">
|
||||
<value>Animations</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: Window tab collapsible section titles (R6) -->
|
||||
<data name="Settings_Section_Hide" xml:space="preserve">
|
||||
<value>Hide</value>
|
||||
</data>
|
||||
<data name="Settings_Section_InactivityHide" xml:space="preserve">
|
||||
<value>Hide when inactive</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Frame" xml:space="preserve">
|
||||
<value>Frame</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: Tabs tab per-tab-item sub-section titles (R6) -->
|
||||
<data name="Settings_Section_Tab_Channels" xml:space="preserve">
|
||||
<value>Channels</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Display" xml:space="preserve">
|
||||
<value>Display</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Notification" xml:space="preserve">
|
||||
<value>Notification</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Input" xml:space="preserve">
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_PopOut" xml:space="preserve">
|
||||
<value>Pop-out window</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Tab_Volume_AllTabsHint" xml:space="preserve">
|
||||
<value>This volume applies to all tabs.</value>
|
||||
</data>
|
||||
|
||||
<!-- v1.5.6: About tab collapsible section titles (R6) -->
|
||||
<data name="Settings_Section_Extensions" xml:space="preserve">
|
||||
<value>Extensions</value>
|
||||
</data>
|
||||
<data name="Settings_Section_PluginInfo" xml:space="preserve">
|
||||
<value>Plugin info</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Project" xml:space="preserve">
|
||||
<value>The Project</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Translators" xml:space="preserve">
|
||||
<value>Translators</value>
|
||||
</data>
|
||||
<data name="Settings_Section_Changelog" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Executable → Regular
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2013 The Exo 2 Project Authors (https://github.com/googlefonts/Exo-2.0)
|
||||
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
+18
@@ -1860,6 +1860,24 @@ namespace HellionChat.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Latin Extended.
|
||||
/// </summary>
|
||||
internal static string ExtraGlyphRanges_LatinExtended_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ExtraGlyphRanges_LatinExtended_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Greek.
|
||||
/// </summary>
|
||||
internal static string ExtraGlyphRanges_Greek_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ExtraGlyphRanges_Greek_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Pick a folder location for export..
|
||||
/// </summary>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Tenyeix el selector de canal amb el color del canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>El botó selector de canal al costat del camp d'entrada es tenyeix amb el color del canal actiu. Coincideix amb la tonalitat del text d'entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Amaga mentre el menú New Game+ estigui obert</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Amaga el xat mentre el menú New Game+ estigui obert. En tancar el menú, el xat torna a aparèixer.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Llatí estès</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grec</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+20
@@ -1,4 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Language.de.resx — Hellion Forge maintainer-extended translation
|
||||
|
||||
Locale: de (German)
|
||||
Maintainer: Hellion Forge / Hellion Online Media
|
||||
Status: Native-speaker maintained
|
||||
Review: Continuous (native maintainer)
|
||||
|
||||
Hellion Forge maintains this file with native-speaker quality,
|
||||
including the keys post-dating the last upstream Chat 2 Crowdin sync.
|
||||
|
||||
Corrections welcome via the Hellion Forge Discord:
|
||||
https://discord.gg/X9V7Kcv5gR
|
||||
-->
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
@@ -1481,4 +1495,10 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latein erweitert</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Griechisch</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Teñir el selector de canal con el color del canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>El botón selector de canal junto al campo de entrada se tiñe con el color del canal activo. Coincide con el tinte del texto de entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ocultar mientras el menú New Game+ esté abierto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Oculta el chat mientras el menú New Game+ esté abierto. Al cerrar el menú, el chat se muestra de nuevo.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latín extendido</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Griego</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Teinter le sélecteur de canal avec la couleur du canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Le bouton sélecteur de canal à côté du champ de saisie est teinté avec la couleur du canal actif. Correspond à la teinte du texte de saisie.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Masquer pendant que le menu New Game+ est ouvert</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Masque le chat pendant que le menu New Game+ est ouvert. Fermer le menu réaffiche le chat.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latin étendu</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grec</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colora il selettore di canale con il colore del canale</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Il pulsante selettore di canale accanto al campo di input viene colorato con il colore del canale attivo. Corrisponde alla colorazione del testo di input.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Nascondi mentre il menu New Game+ è aperto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Nasconde la chat mentre il menu New Game+ è aperto. Chiudendo il menu, la chat riappare.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latino esteso</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greco</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>チャンネルセレクターをチャンネル色で着色する</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>入力フィールドの隣のチャンネルセレクターボタンが、現在アクティブなチャンネルの色で着色されます。入力テキスト自体の色合いと一致します。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>ニューゲーム+メニューが開いている間は非表示</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>ニューゲーム+メニューが開いている間、チャットを非表示にします。メニューを閉じるとチャットが再表示されます。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拡張ラテン</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>ギリシャ語</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>채널 선택기를 채널 색상으로 채색</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>입력 필드 옆의 채널 선택기 버튼이 현재 활성 채널 색상으로 채색됩니다. 입력 텍스트 자체의 색조와 일치합니다.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>뉴게임+ 메뉴가 열려 있는 동안 숨김</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>뉴게임+ 메뉴가 열려 있는 동안 채팅을 숨깁니다. 메뉴를 닫으면 채팅이 다시 표시됩니다.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>확장 라틴</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>그리스어</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Kanaalkiezer kleuren met kanaalkleur</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>De kanaalkiezerknop naast het invoerveld krijgt de kleur van het actieve kanaal. Komt overeen met de tint van de invoertekst zelf.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Verbergen terwijl het New Game+ menu open is</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Verberg de chat terwijl het New Game+ menu open is. Het sluiten van het menu toont de chat weer.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latijn uitgebreid</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grieks</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colorir o seletor de canal com a cor do canal</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>O botão seletor de canal ao lado do campo de entrada é colorido com a cor do canal ativo. Combina com a coloração do próprio texto de entrada.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ocultar enquanto o menu New Game+ estiver aberto</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Oculta o chat enquanto o menu New Game+ estiver aberto. Fechar o menu mostra o chat novamente.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latim estendido</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grego</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
+1503
File diff suppressed because it is too large
Load Diff
@@ -1478,4 +1478,10 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latin Extended</value>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greek</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Colorează selectorul de canal cu culoarea canalului</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Butonul selector de canal de lângă câmpul de intrare este colorat cu culoarea canalului activ. Se potrivește cu nuanța textului de intrare.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Ascunde cât timp meniul New Game+ este deschis</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Ascunde chatul cât timp meniul New Game+ este deschis. Închiderea meniului afișează chatul din nou.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Latină extinsă</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Greacă</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Окрашивать кнопку выбора канала в цвет канала</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Кнопка выбора канала рядом с полем ввода окрашивается в цвет активного канала. Совпадает с окраской самого текста ввода.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Скрывать, пока открыто меню New Game+</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Скрывать чат, пока открыто меню New Game+. При закрытии меню чат снова отображается.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Расширенная латиница</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Греческий</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>Färga kanalväljaren med kanalens färg</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>Kanalväljarknappen bredvid inmatningsfältet färgas med den aktiva kanalens färg. Matchar färgningen av själva inmatningstexten.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>Dölj medan New Game+ menyn är öppen</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>Dölj chatten medan New Game+ menyn är öppen. När menyn stängs visas chatten igen.</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>Utökat latin</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>Grekiska</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Generated
+1503
File diff suppressed because it is too large
Load Diff
Generated
+1503
File diff suppressed because it is too large
Load Diff
+24
@@ -1466,4 +1466,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>用频道颜色为频道选择器染色</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>输入框旁边的频道选择器按钮将以当前活动频道的颜色着色。与输入文本本身的着色相匹配。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>在新游戏+菜单打开时隐藏</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>在新游戏+菜单打开时隐藏聊天。关闭菜单时聊天会再次显示。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拉丁文扩展</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>希腊语</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
+24
@@ -1467,4 +1467,28 @@ Your old database can still be recovered, please contact the plugin author for h
|
||||
<data name="ChatExport_Initial" xml:space="preserve">
|
||||
<value>Loading logs ...</value>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Name" xml:space="preserve">
|
||||
<value>用頻道顏色為頻道選擇器染色</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_ColorSelectedInputChannelButton_Description" xml:space="preserve">
|
||||
<value>輸入框旁邊的頻道選擇器按鈕將以當前活動頻道的顏色著色。與輸入文字本身的著色相匹配。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Name" xml:space="preserve">
|
||||
<value>在新遊戲+選單開啟時隱藏</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="Options_HideInNewGamePlusMenu_Description" xml:space="preserve">
|
||||
<value>在新遊戲+選單開啟時隱藏聊天。關閉選單時聊天會再次顯示。</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_LatinExtended_Name" xml:space="preserve">
|
||||
<value>拉丁文擴展</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
<data name="ExtraGlyphRanges_Greek_Name" xml:space="preserve">
|
||||
<value>希臘文</value>
|
||||
<comment>AI-assisted machine translation. Pending native-speaker review.</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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() { }
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
using HellionChat.Resources;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Verifies the v1.5.4 PM-2 quick-picker plumbing without rendering:
|
||||
// resource strings resolve, the theme registry yields the expected
|
||||
// minimum built-in count, and Config.Tabs is populated.
|
||||
internal sealed class QuickPickerSelfTestStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
public QuickPickerSelfTestStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - Quick picker plumbing";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(HellionStrings.Settings_QuickPicker_Tooltip))
|
||||
{
|
||||
ImGui.Text("Settings_QuickPicker_Tooltip is empty in the active locale.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(HellionStrings.Settings_QuickPicker_Themes_Header))
|
||||
{
|
||||
ImGui.Text("Settings_QuickPicker_Themes_Header is empty in the active locale.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(HellionStrings.Settings_QuickPicker_Tabs_Header))
|
||||
{
|
||||
ImGui.Text("Settings_QuickPicker_Tabs_Header is empty in the active locale.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
var registry = this.plugin.ThemeRegistry;
|
||||
if (registry is null)
|
||||
{
|
||||
ImGui.Text("ThemeRegistry not resolved.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
var builtIns = registry.AllBuiltIns().ToList();
|
||||
if (builtIns.Count < 10)
|
||||
{
|
||||
ImGui.Text($"Expected at least 10 built-in themes, found {builtIns.Count}.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
var tabs = Plugin.Config.Tabs;
|
||||
if (tabs is null || tabs.Count == 0)
|
||||
{
|
||||
ImGui.Text("Config.Tabs is empty.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp() { }
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Plugin.SelfTest;
|
||||
using HellionChat.Themes;
|
||||
|
||||
namespace HellionChat.SelfTests;
|
||||
|
||||
// Verifies the v1.5.4 PM-1 crossfade contract: switching the active
|
||||
// theme arms TryGetActiveCrossfade for ~300ms, then the registry
|
||||
// returns to direct AbgrCache reads. A second switch within 100ms
|
||||
// keeps the lerped path active (no identity-snap). CleanUp restores
|
||||
// the initial theme so /xlperf stays idempotent.
|
||||
internal sealed class ThemeCrossfadeSelfTestStep : ISelfTestStep
|
||||
{
|
||||
private readonly Plugin plugin;
|
||||
|
||||
private string? initialSlug;
|
||||
private string? targetSlug;
|
||||
private string? midSwitchSlug;
|
||||
private long armedAtTickMs = long.MinValue;
|
||||
private long midArmedAtTickMs = long.MinValue;
|
||||
private bool sawCrossfade;
|
||||
private bool sawMidCrossfadeSwitch;
|
||||
private bool sawCrossfadeEnd;
|
||||
private bool restoredInitial;
|
||||
|
||||
public ThemeCrossfadeSelfTestStep(Plugin plugin)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public string Name => "Hellion Chat - Theme crossfade";
|
||||
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
var registry = this.plugin.ThemeRegistry;
|
||||
if (registry is null)
|
||||
return SelfTestStepResult.Fail;
|
||||
|
||||
if (this.initialSlug is null)
|
||||
{
|
||||
this.initialSlug = registry.Active.Slug;
|
||||
this.targetSlug = PickDifferentSlug(registry, this.initialSlug);
|
||||
if (this.targetSlug is null)
|
||||
{
|
||||
ImGui.Text("Need at least two themes available; only one built-in found.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
registry.Switch(this.targetSlug);
|
||||
this.armedAtTickMs = Environment.TickCount64;
|
||||
ImGui.Text($"Crossfade armed: {this.initialSlug} -> {this.targetSlug}");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
if (!this.sawCrossfade)
|
||||
{
|
||||
if (registry.TryGetActiveCrossfade(out _))
|
||||
{
|
||||
this.sawCrossfade = true;
|
||||
this.midArmedAtTickMs = Environment.TickCount64;
|
||||
ImGui.Text("Crossfade observed mid-window, arming mid-switch test...");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
// If the window already closed before we observed it, that
|
||||
// is acceptable only on extremely slow frame paths; accept
|
||||
// it as "saw the start" if more than 300ms have elapsed.
|
||||
// Skip the mid-crossfade-switch phase in that case -- the
|
||||
// lerped path is no longer active, so a second switch would
|
||||
// re-arm a fresh crossfade and not exercise PM-1b's
|
||||
// mid-flight-origin override.
|
||||
if (Environment.TickCount64 - this.armedAtTickMs > 300)
|
||||
{
|
||||
this.sawCrossfade = true;
|
||||
this.sawMidCrossfadeSwitch = true;
|
||||
this.sawCrossfadeEnd = true;
|
||||
ImGui.Text("Crossfade window closed before observation; accepting.");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
if (!this.sawMidCrossfadeSwitch)
|
||||
{
|
||||
// PM-Test-3 mid-crossfade-switch phase: within ~100ms of the
|
||||
// first observed crossfade, fire a second Switch to a THIRD
|
||||
// theme. ArmCrossfade must compose the current lerped state
|
||||
// as the new origin -- TryGetActiveCrossfade still returns
|
||||
// true (lerped path stays active, no identity-snap) and the
|
||||
// lerped value is neither the identity-from nor the
|
||||
// identity-to of the new switch (origin shifted to the
|
||||
// mid-flight cache, target is the third theme).
|
||||
if (Environment.TickCount64 - this.midArmedAtTickMs < 100)
|
||||
{
|
||||
this.midSwitchSlug = PickDifferentSlug(
|
||||
registry,
|
||||
[this.initialSlug!, this.targetSlug!]
|
||||
);
|
||||
if (this.midSwitchSlug is null)
|
||||
{
|
||||
// Only two themes available -- mid-switch phase cannot
|
||||
// exercise the lerped-origin path. Accept and move on
|
||||
// (the v1.5.3 baseline ships >=10 built-ins, so this
|
||||
// branch is defensive).
|
||||
this.sawMidCrossfadeSwitch = true;
|
||||
ImGui.Text("Only two themes available; skipping mid-switch assert.");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
var fromCache = registry.Active.AbgrCache;
|
||||
registry.Switch(this.midSwitchSlug);
|
||||
var toCache = registry.Active.AbgrCache;
|
||||
|
||||
if (!registry.TryGetActiveCrossfade(out var midLerped))
|
||||
{
|
||||
ImGui.Text("Mid-switch failed: TryGetActiveCrossfade returned false.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
// Lerped value must be neither the new identity-from
|
||||
// (target cache of the first switch) nor the new
|
||||
// identity-to (third theme cache) -- it must originate
|
||||
// from the mid-flight composed snapshot.
|
||||
if (midLerped.Equals(fromCache) || midLerped.Equals(toCache))
|
||||
{
|
||||
ImGui.Text("Mid-switch failed: lerped value is an identity snap.");
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
|
||||
this.sawMidCrossfadeSwitch = true;
|
||||
ImGui.Text(
|
||||
$"Mid-switch armed: {this.targetSlug} -> {this.midSwitchSlug} (lerped origin)."
|
||||
);
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
// Window for mid-switch already elapsed; accept and continue.
|
||||
this.sawMidCrossfadeSwitch = true;
|
||||
ImGui.Text("Mid-switch window elapsed before fire; accepting.");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
if (!this.sawCrossfadeEnd)
|
||||
{
|
||||
if (!registry.TryGetActiveCrossfade(out _))
|
||||
{
|
||||
this.sawCrossfadeEnd = true;
|
||||
ImGui.Text("Crossfade window closed cleanly.");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
if (!this.restoredInitial)
|
||||
{
|
||||
registry.Switch(this.initialSlug);
|
||||
this.restoredInitial = true;
|
||||
ImGui.Text($"Restored: {this.initialSlug}");
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
// Wait for the restore-crossfade to also conclude before
|
||||
// declaring Pass, so /xlperf does not flicker out mid-fade.
|
||||
if (registry.TryGetActiveCrossfade(out _))
|
||||
return SelfTestStepResult.Waiting;
|
||||
|
||||
return SelfTestStepResult.Pass;
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
// Best-effort: if anything went sideways, snap back to the
|
||||
// initial slug. Switch is idempotent on same-slug.
|
||||
var registry = this.plugin.ThemeRegistry;
|
||||
if (registry is not null && this.initialSlug is not null)
|
||||
{
|
||||
registry.Switch(this.initialSlug);
|
||||
}
|
||||
|
||||
this.initialSlug = null;
|
||||
this.targetSlug = null;
|
||||
this.midSwitchSlug = null;
|
||||
this.armedAtTickMs = long.MinValue;
|
||||
this.midArmedAtTickMs = long.MinValue;
|
||||
this.sawCrossfade = false;
|
||||
this.sawMidCrossfadeSwitch = false;
|
||||
this.sawCrossfadeEnd = false;
|
||||
this.restoredInitial = false;
|
||||
}
|
||||
|
||||
private static string? PickDifferentSlug(ThemeRegistry registry, string activeSlug) =>
|
||||
PickDifferentSlug(registry, [activeSlug]);
|
||||
|
||||
private static string? PickDifferentSlug(
|
||||
ThemeRegistry registry,
|
||||
IReadOnlyCollection<string> excludeSlugs
|
||||
)
|
||||
{
|
||||
foreach (var theme in registry.AllBuiltIns())
|
||||
{
|
||||
var match = false;
|
||||
foreach (var excluded in excludeSlugs)
|
||||
{
|
||||
if (string.Equals(theme.Slug, excluded, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match)
|
||||
return theme.Slug;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
namespace HellionChat.Themes;
|
||||
|
||||
// Per-slot ABGR byte-lerp between two ThemeAbgrCache value-records.
|
||||
// Pattern anchor: imgui.cpp:2820-2828 ImAlphaBlendColors -- decompose
|
||||
// each byte, lerp via Math.Round, recompose. Stack-allocated output
|
||||
// (readonly record struct), no heap pressure inside the crossfade
|
||||
// window. t is clamped to [0, 1] so float drift cannot overshoot.
|
||||
internal static class ThemeAbgrCacheLerp
|
||||
{
|
||||
public static ThemeAbgrCache Lerp(ThemeAbgrCache from, ThemeAbgrCache to, float t)
|
||||
{
|
||||
t = Math.Clamp(t, 0f, 1f);
|
||||
|
||||
return new ThemeAbgrCache(
|
||||
PrimaryDark: LerpAbgr(from.PrimaryDark, to.PrimaryDark, t),
|
||||
Primary: LerpAbgr(from.Primary, to.Primary, t),
|
||||
PrimaryLight: LerpAbgr(from.PrimaryLight, to.PrimaryLight, t),
|
||||
PrimaryGlow: LerpAbgr(from.PrimaryGlow, to.PrimaryGlow, t),
|
||||
AccentDark: LerpAbgr(from.AccentDark, to.AccentDark, t),
|
||||
Accent: LerpAbgr(from.Accent, to.Accent, t),
|
||||
AccentLight: LerpAbgr(from.AccentLight, to.AccentLight, t),
|
||||
Identity: LerpAbgr(from.Identity, to.Identity, t),
|
||||
WindowBg: LerpAbgr(from.WindowBg, to.WindowBg, t),
|
||||
ChildBg: LerpAbgr(from.ChildBg, to.ChildBg, t),
|
||||
FrameBg: LerpAbgr(from.FrameBg, to.FrameBg, t),
|
||||
Surface: LerpAbgr(from.Surface, to.Surface, t),
|
||||
SurfaceHover: LerpAbgr(from.SurfaceHover, to.SurfaceHover, t),
|
||||
Border: LerpAbgr(from.Border, to.Border, t),
|
||||
TextPrimary: LerpAbgr(from.TextPrimary, to.TextPrimary, t),
|
||||
TextMuted: LerpAbgr(from.TextMuted, to.TextMuted, t),
|
||||
TextDim: LerpAbgr(from.TextDim, to.TextDim, t),
|
||||
StatusSuccess: LerpAbgr(from.StatusSuccess, to.StatusSuccess, t),
|
||||
StatusDanger: LerpAbgr(from.StatusDanger, to.StatusDanger, t),
|
||||
StatusWarning: LerpAbgr(from.StatusWarning, to.StatusWarning, t),
|
||||
StatusInfo: LerpAbgr(from.StatusInfo, to.StatusInfo, t)
|
||||
);
|
||||
}
|
||||
|
||||
private static uint LerpAbgr(uint from, uint to, float t)
|
||||
{
|
||||
var ra = (byte)(from & 0xFFu);
|
||||
var ga = (byte)((from >> 8) & 0xFFu);
|
||||
var ba = (byte)((from >> 16) & 0xFFu);
|
||||
var aa = (byte)((from >> 24) & 0xFFu);
|
||||
|
||||
var rb = (byte)(to & 0xFFu);
|
||||
var gb = (byte)((to >> 8) & 0xFFu);
|
||||
var bb = (byte)((to >> 16) & 0xFFu);
|
||||
var ab = (byte)((to >> 24) & 0xFFu);
|
||||
|
||||
// Math.Round (default ToEven) matches ColourUtil.ApplyAlpha so a
|
||||
// crossfade-into-hover transition does not produce a one-byte
|
||||
// jump at the midpoint between the two paths.
|
||||
var r = (byte)Math.Round(ra + (rb - ra) * t);
|
||||
var g = (byte)Math.Round(ga + (gb - ga) * t);
|
||||
var b = (byte)Math.Round(ba + (bb - ba) * t);
|
||||
var a = (byte)Math.Round(aa + (ab - aa) * t);
|
||||
|
||||
return ((uint)a << 24) | ((uint)b << 16) | ((uint)g << 8) | r;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,16 @@ public sealed class ThemeRegistry
|
||||
private long _lastActiveStampCheckMs = -ActiveStampPollIntervalMs;
|
||||
private DateTime _lastActiveStamp = DateTime.MinValue;
|
||||
|
||||
// PM-1 crossfade state. Switch() captures the previous AbgrCache as a
|
||||
// VALUE-COPY (not a Theme reference) -- the built-in singletons share
|
||||
// their RecomputeAbgrCache identity, so a reference would mutate
|
||||
// alongside the new active. _crossfadeStartTickMs == long.MinValue
|
||||
// means "no crossfade armed yet"; the field stays MinValue after
|
||||
// SwitchSilent so the plugin-load init-path does not trigger a fade.
|
||||
private ThemeAbgrCache? _previousAbgrSnapshot;
|
||||
private long _crossfadeStartTickMs = long.MinValue;
|
||||
private const int CrossfadeDurationMs = 300;
|
||||
|
||||
public ThemeRegistry(string? customThemesDir = null, ILogger<ThemeRegistry>? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -87,6 +97,13 @@ public sealed class ThemeRegistry
|
||||
// a state where _active and Get(_active.Slug) disagree.
|
||||
public void Switch(string slug)
|
||||
{
|
||||
// Same-slug switch is a no-op -- avoids a 300ms identity-crossfade
|
||||
// when the user re-selects the active theme in the picker.
|
||||
if (string.Equals(_active.Slug, slug, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
ArmCrossfade();
|
||||
|
||||
if (_builtIns.TryGetValue(slug, out var builtin))
|
||||
{
|
||||
_active = builtin;
|
||||
@@ -115,6 +132,84 @@ public sealed class ThemeRegistry
|
||||
_activeCustomPath = null;
|
||||
}
|
||||
|
||||
// SwitchSilent is the plugin-load init path -- identical to Switch
|
||||
// but does NOT arm the crossfade state. Called from
|
||||
// ThemeRegistryInitHostedService.StartAsync so opening the plugin
|
||||
// does not produce a 300ms fade from the default theme to the user's
|
||||
// saved theme.
|
||||
public void SwitchSilent(string slug)
|
||||
{
|
||||
if (string.Equals(_active.Slug, slug, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
if (_builtIns.TryGetValue(slug, out var builtin))
|
||||
{
|
||||
_active = builtin;
|
||||
_active.RecomputeAbgrCache();
|
||||
_activeCustomPath = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var customTheme = LoadCustomBySlug(slug, out var customPath);
|
||||
if (customTheme is not null)
|
||||
{
|
||||
_active = customTheme;
|
||||
_active.RecomputeAbgrCache();
|
||||
_activeCustomPath = customPath;
|
||||
_lastActiveStamp = DateTime.MinValue;
|
||||
return;
|
||||
}
|
||||
|
||||
_active = _builtIns[DefaultSlug];
|
||||
_active.RecomputeAbgrCache();
|
||||
_activeCustomPath = null;
|
||||
}
|
||||
|
||||
// Captures the AbgrCache snapshot that PushGlobal should fade FROM.
|
||||
// If a crossfade is already mid-flight (second Switch within 300ms),
|
||||
// the current lerped state replaces the snapshot -- the next fade
|
||||
// starts from where we currently are, not from the original "from".
|
||||
private void ArmCrossfade()
|
||||
{
|
||||
var now = Environment.TickCount64;
|
||||
ThemeAbgrCache snapshot;
|
||||
if (
|
||||
_previousAbgrSnapshot.HasValue
|
||||
&& _crossfadeStartTickMs != long.MinValue
|
||||
&& now - _crossfadeStartTickMs < CrossfadeDurationMs
|
||||
)
|
||||
{
|
||||
var t = (float)(now - _crossfadeStartTickMs) / CrossfadeDurationMs;
|
||||
snapshot = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
snapshot = _active.AbgrCache;
|
||||
}
|
||||
|
||||
_previousAbgrSnapshot = snapshot;
|
||||
_crossfadeStartTickMs = now;
|
||||
}
|
||||
|
||||
// Returns the lerped AbgrCache while the crossfade is active.
|
||||
// PushGlobal reads this once per frame; outside the 300ms window
|
||||
// it short-circuits via the TickCount64 delta so the per-frame
|
||||
// overhead is a couple of integer comparisons.
|
||||
public bool TryGetActiveCrossfade(out ThemeAbgrCache lerped)
|
||||
{
|
||||
lerped = default;
|
||||
if (_crossfadeStartTickMs == long.MinValue || !_previousAbgrSnapshot.HasValue)
|
||||
return false;
|
||||
|
||||
var elapsed = Environment.TickCount64 - _crossfadeStartTickMs;
|
||||
if (elapsed >= CrossfadeDurationMs)
|
||||
return false;
|
||||
|
||||
var t = (float)elapsed / CrossfadeDurationMs;
|
||||
lerped = ThemeAbgrCacheLerp.Lerp(_previousAbgrSnapshot.Value, _active.AbgrCache, t);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1Hz-throttled disk-stat on the currently active custom theme file.
|
||||
// When the file's LastWriteTime moves forward (editor save), reload the
|
||||
// theme via Get() so the user sees the edit immediately without
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
@@ -19,6 +21,11 @@ public sealed class ChatInputBar
|
||||
private readonly Func<Tab?> _activeTabAccessor;
|
||||
private readonly InputState _state = new();
|
||||
|
||||
// UI-11: the buffer for which a plugin-disclosure warning was already
|
||||
// shown. A second Enter on the same buffer sends it anyway; editing the
|
||||
// buffer clears the arming so the next send is re-checked.
|
||||
private string? _disclosureArmedBuffer;
|
||||
|
||||
public ChatInputBar(Plugin plugin, ChatLogWindow host, Func<Tab?> activeTabAccessor)
|
||||
{
|
||||
_plugin = plugin;
|
||||
@@ -80,11 +87,40 @@ public sealed class ChatInputBar
|
||||
{
|
||||
SubmitCompact(tab);
|
||||
}
|
||||
|
||||
// UI-11: disclosure warning, visible only while an armed buffer is held
|
||||
// unchanged. Editing the buffer clears the condition automatically.
|
||||
if (
|
||||
Plugin.Config.NotifyPluginDisclosure
|
||||
&& _disclosureArmedBuffer is not null
|
||||
&& _state.Buffer == _disclosureArmedBuffer
|
||||
)
|
||||
{
|
||||
ImGui.TextColored(
|
||||
ImGuiColors.DalamudYellow,
|
||||
HellionStrings.ChatInput_PluginDisclosure_Warning
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TEST-MIRROR: ../_Helpers/CompactInputSubmitter.cs
|
||||
private void SubmitCompact(Tab tab) =>
|
||||
private void SubmitCompact(Tab tab)
|
||||
{
|
||||
if (
|
||||
Plugin.Config.NotifyPluginDisclosure
|
||||
&& _state.Buffer != _disclosureArmedBuffer
|
||||
&& PluginDisclosureScanner.ContainsPrivateUseGlyph(_state.Buffer)
|
||||
)
|
||||
{
|
||||
// First send attempt on this exact buffer: arm and hold. The buffer
|
||||
// is kept, the warning renders, the user can press Enter again.
|
||||
_disclosureArmedBuffer = _state.Buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
_disclosureArmedBuffer = null;
|
||||
CompactInputSubmitter.TrySubmit(_state, tab, _host.SendChatBoxFromExternal);
|
||||
}
|
||||
|
||||
// History navigation callback. Cursor math delegated to
|
||||
// CompactInputHistoryNavigator; ImGui buffer splice stays here.
|
||||
|
||||
+368
-20
@@ -8,12 +8,15 @@ using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Memory;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using HellionChat._Helpers;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.GameFunctions;
|
||||
using HellionChat.GameFunctions.Types;
|
||||
@@ -54,6 +57,11 @@ public sealed class ChatLogWindow : Window
|
||||
private int ActivatePos = -1;
|
||||
internal string Chat = string.Empty;
|
||||
|
||||
// UI-11: the main-window input buffer for which a plugin-disclosure
|
||||
// warning was already shown. Mirrors _disclosureArmedBuffer in
|
||||
// ChatInputBar — a second Enter on the same buffer sends it anyway.
|
||||
private string? _disclosureArmedBufferMain;
|
||||
|
||||
// Input history extracted into InputHistoryService so pop-out windows share
|
||||
// the same Up/Down history. Cursor stays window-local (independent navigation).
|
||||
private int InputBacklogIdx = -1;
|
||||
@@ -472,6 +480,105 @@ public sealed class ChatLogWindow : Window
|
||||
ChangeTab(newIndex);
|
||||
}
|
||||
|
||||
// PM-2b v1.5.4 header quick-picker. Two scrollable sections -- every
|
||||
// built-in plus custom theme, and every tab. Clicking a theme arms
|
||||
// the PM-1 crossfade via ThemeRegistry.Switch; clicking a tab routes
|
||||
// through ChangeTab so LastActivityTime stays consistent with the
|
||||
// sidebar and top-bar click paths. DontClosePopups keeps the popup
|
||||
// open so the user can hop between entries without re-opening it.
|
||||
private void DrawQuickPickerPopup()
|
||||
{
|
||||
using var popup = ImRaii.Popup("##hellion-quick-picker");
|
||||
if (!popup.Success)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(HellionStrings.Settings_QuickPicker_Themes_Header);
|
||||
ImGui.Separator();
|
||||
|
||||
var activeSlug = Plugin.ThemeRegistry.Active.Slug;
|
||||
var allThemes = Plugin
|
||||
.ThemeRegistry.AllBuiltIns()
|
||||
.Concat(Plugin.ThemeRegistry.AllCustom())
|
||||
.ToList();
|
||||
|
||||
using (
|
||||
var scroll = ImRaii.Child(
|
||||
"##hellion-quick-picker-themes",
|
||||
new Vector2(220f, Math.Min(allThemes.Count * 22f, 200f))
|
||||
)
|
||||
)
|
||||
{
|
||||
if (scroll.Success)
|
||||
{
|
||||
foreach (var theme in allThemes)
|
||||
{
|
||||
var isActive = string.Equals(
|
||||
theme.Slug,
|
||||
activeSlug,
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
DrawQuickPickerGlyph(isActive);
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
$"{theme.Name}##quick-theme-{theme.Slug}",
|
||||
isActive,
|
||||
ImGuiSelectableFlags.DontClosePopups
|
||||
) && !isActive
|
||||
)
|
||||
Plugin.ThemeRegistry.Switch(theme.Slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.Settings_QuickPicker_Tabs_Header);
|
||||
ImGui.Separator();
|
||||
|
||||
var tabs = Plugin.Config.Tabs;
|
||||
var activeTabIndex = Plugin.LastTab;
|
||||
using (
|
||||
var scroll = ImRaii.Child(
|
||||
"##hellion-quick-picker-tabs",
|
||||
new Vector2(220f, Math.Min(tabs.Count * 22f, 200f))
|
||||
)
|
||||
)
|
||||
{
|
||||
if (scroll.Success)
|
||||
{
|
||||
for (var i = 0; i < tabs.Count; i++)
|
||||
{
|
||||
var isActive = i == activeTabIndex;
|
||||
DrawQuickPickerGlyph(isActive);
|
||||
if (
|
||||
ImGui.Selectable(
|
||||
$"{tabs[i].Name}##quick-tab-{i}",
|
||||
isActive,
|
||||
ImGuiSelectableFlags.DontClosePopups
|
||||
) && !isActive
|
||||
)
|
||||
ChangeTab(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Leading check-glyph slot for a quick-picker row. Active rows get a
|
||||
// FontAwesome check; inactive rows get a same-width blank so the
|
||||
// labels stay aligned. The glyph font push stays on its own line so
|
||||
// it never bleeds into the body-font Selectable label.
|
||||
private void DrawQuickPickerGlyph(bool isActive)
|
||||
{
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
{
|
||||
var check = FontAwesomeIcon.Check.ToIconString();
|
||||
if (isActive)
|
||||
ImGui.TextUnformatted(check);
|
||||
else
|
||||
ImGui.Dummy(new Vector2(ImGui.CalcTextSize(check).X, ImGui.GetTextLineHeight()));
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
private void TabSwitched(Tab newTab, Tab previousTab)
|
||||
{
|
||||
// Use the fixed channel if set by the user. Otherwise, if the new tab
|
||||
@@ -609,7 +716,13 @@ public sealed class ChatLogWindow : Window
|
||||
// Window-Deckkraft eingestellt hat, hat dieses Per-Window-Override
|
||||
// Vorrang über unseren Slider — wir dokumentieren das im HelpMarker.
|
||||
if (LastViewport == ImGuiHelpers.MainViewport.Handle && !WasDocked)
|
||||
BgAlpha = Plugin.Config.WindowOpacity;
|
||||
{
|
||||
// UI-12: focus-dependent opacity. PreOpenCheck runs before Begin();
|
||||
// Window.IsFocused holds last frame's RootAndChildWindows focus, set
|
||||
// by Dalamud's WindowHost after Begin(). One-frame latency is
|
||||
// accepted.
|
||||
BgAlpha = IsFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive;
|
||||
}
|
||||
|
||||
LastViewport = ImGui.GetWindowViewport().Handle;
|
||||
WasDocked = ImGui.IsWindowDocked();
|
||||
@@ -677,6 +790,14 @@ public sealed class ChatLogWindow : Window
|
||||
// (~17ms at 60fps) late, invisible inside the post-reload Atlas-Build.
|
||||
private bool _firstFrameDone;
|
||||
|
||||
// Set when the user clicks the scroll-to-bottom button; the next
|
||||
// frame's scroll-snap check forces a jump to the live end.
|
||||
private bool _scrollToBottomRequested;
|
||||
|
||||
// Cached each frame inside the ##chat2-messages child. True when the
|
||||
// user has scrolled up enough that the toolbar button should be shown.
|
||||
private bool _childScrolledUp;
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
DrewThisFrame = true;
|
||||
@@ -903,7 +1024,15 @@ public sealed class ChatLogWindow : Window
|
||||
var buttonWidth = afterIcon.X - beforeIcon.X;
|
||||
var showNovice = Plugin.Config.ShowNoviceNetwork && GameFunctions.GameFunctions.IsMentor();
|
||||
var buttonsRight = (showNovice ? 1 : 0) + (Plugin.Config.ShowHideButton ? 1 : 0);
|
||||
var inputWidth = ImGui.GetContentRegionAvail().X - buttonWidth * (1 + buttonsRight);
|
||||
// Right-side buttons: quick-picker palette + cog (always present)
|
||||
// plus the optional hide / novice buttons. Each slot costs the
|
||||
// measured button width AND one ItemSpacing for the SameLine gap
|
||||
// in front of it -- leaving the spacing term out overflows the
|
||||
// header row by one gap per button (v1.5.4 quick-picker fix).
|
||||
var rightButtonCount = 2 + buttonsRight;
|
||||
var inputWidth =
|
||||
ImGui.GetContentRegionAvail().X
|
||||
- rightButtonCount * (buttonWidth + ImGui.GetStyle().ItemSpacing.X);
|
||||
|
||||
var normalColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var push = inputColour != null;
|
||||
@@ -957,6 +1086,10 @@ public sealed class ChatLogWindow : Window
|
||||
{
|
||||
Chat = chatCopy;
|
||||
|
||||
// UI-11: Escape cancels the input — drop any pending
|
||||
// disclosure arming so the warning does not linger.
|
||||
_disclosureArmedBufferMain = null;
|
||||
|
||||
if (activeTab.CurrentChannel.UseTempChannel)
|
||||
{
|
||||
activeTab.CurrentChannel.ResetTempChannel();
|
||||
@@ -966,6 +1099,19 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
if (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter))
|
||||
{
|
||||
if (
|
||||
Plugin.Config.NotifyPluginDisclosure
|
||||
&& Chat != _disclosureArmedBufferMain
|
||||
&& PluginDisclosureScanner.ContainsPrivateUseGlyph(Chat)
|
||||
)
|
||||
{
|
||||
// First send attempt on this exact buffer: arm and hold.
|
||||
// The warning renders below the input.
|
||||
_disclosureArmedBufferMain = Chat;
|
||||
}
|
||||
else
|
||||
{
|
||||
_disclosureArmedBufferMain = null;
|
||||
Plugin.CommandHelpWindow.IsOpen = false;
|
||||
SendChatBox(activeTab);
|
||||
|
||||
@@ -976,6 +1122,22 @@ public sealed class ChatLogWindow : Window
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI-11: disclosure warning for the main-window input, mirrors the
|
||||
// ChatInputBar path. Visible only while the armed buffer is held
|
||||
// unchanged; editing the buffer clears the condition.
|
||||
if (
|
||||
Plugin.Config.NotifyPluginDisclosure
|
||||
&& _disclosureArmedBufferMain is not null
|
||||
&& Chat == _disclosureArmedBufferMain
|
||||
)
|
||||
{
|
||||
ImGui.TextColored(
|
||||
ImGuiColors.DalamudYellow,
|
||||
HellionStrings.ChatInput_PluginDisclosure_Warning
|
||||
);
|
||||
}
|
||||
|
||||
// Process keybinds that have modifiers while the chat is focused.
|
||||
if (inputActive)
|
||||
@@ -1007,9 +1169,54 @@ public sealed class ChatLogWindow : Window
|
||||
using var pushedColor = ImRaii.PushColor(ImGuiCol.Text, normalColor);
|
||||
if (ImGui.Selectable(Language.ChatLog_HideChat))
|
||||
UserHide();
|
||||
|
||||
// Insert game text-macro tokens. The game expands <flag>/<item> at
|
||||
// send time, so inserting literal token text is enough. Each entry is
|
||||
// disabled when its precondition is unmet (no map flag, no linked item)
|
||||
// so the inserted token cannot expand to nothing.
|
||||
unsafe
|
||||
{
|
||||
// Null-check before deref: pointers can be null during zone transitions.
|
||||
var agentMap = AgentMap.Instance();
|
||||
var flagSet = agentMap != null && agentMap->FlagMarkerCount > 0;
|
||||
using (ImRaii.Disabled(!flagSet))
|
||||
{
|
||||
if (ImGui.Selectable(HellionStrings.ChatLog_Insert_MapFlag))
|
||||
{
|
||||
Chat += "<flag>";
|
||||
Activate = true;
|
||||
ActivatePos = Chat.Length;
|
||||
}
|
||||
}
|
||||
|
||||
var agentChat = AgentChatLog.Instance();
|
||||
var itemSet = agentChat != null && agentChat->LinkedItem.ItemId != 0;
|
||||
using (ImRaii.Disabled(!itemSet))
|
||||
{
|
||||
if (ImGui.Selectable(HellionStrings.ChatLog_Insert_ItemLink))
|
||||
{
|
||||
Chat += "<item>";
|
||||
Activate = true;
|
||||
ActivatePos = Chat.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.Palette,
|
||||
tooltip: HellionStrings.Settings_QuickPicker_Tooltip,
|
||||
width: (int)buttonWidth
|
||||
)
|
||||
)
|
||||
ImGui.OpenPopup("##hellion-quick-picker");
|
||||
|
||||
DrawQuickPickerPopup();
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
@@ -1420,17 +1627,32 @@ public sealed class ChatLogWindow : Window
|
||||
Tab tab,
|
||||
PayloadHandler handler,
|
||||
float childHeight,
|
||||
bool switchedTab
|
||||
bool switchedTab,
|
||||
bool updateScrollState = true
|
||||
)
|
||||
{
|
||||
using var child = ImRaii.Child("##chat2-messages", new Vector2(-1, childHeight));
|
||||
if (!child.Success)
|
||||
return;
|
||||
|
||||
using (var child = ImRaii.Child("##chat2-messages", new Vector2(-1, childHeight)))
|
||||
{
|
||||
if (child.Success)
|
||||
{
|
||||
if (tab.DisplayTimestamp && Plugin.Config.PrettierTimestamps)
|
||||
DrawLogTableStyle(tab, handler, switchedTab);
|
||||
else
|
||||
DrawLogNormalStyle(tab, handler, switchedTab);
|
||||
|
||||
// Cached for the header toolbar's scroll-to-bottom button, which is
|
||||
// drawn one frame later. GetScrollMaxY / GetScrollY here refer to
|
||||
// the child's scroll context. Pop-out windows pass updateScrollState:
|
||||
// false so they do not overwrite the main window's cached state.
|
||||
if (updateScrollState)
|
||||
_childScrolledUp = ImGui.GetScrollMaxY() - ImGui.GetScrollY() > 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (updateScrollState)
|
||||
_childScrolledUp = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLogNormalStyle(Tab tab, PayloadHandler handler, bool switchedTab)
|
||||
@@ -1438,8 +1660,9 @@ public sealed class ChatLogWindow : Window
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
DrawMessages(tab, handler, false);
|
||||
|
||||
if (switchedTab || ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
|
||||
if (switchedTab || _scrollToBottomRequested || ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
|
||||
ImGui.SetScrollHereY(1f);
|
||||
_scrollToBottomRequested = false;
|
||||
|
||||
handler.Draw();
|
||||
}
|
||||
@@ -1468,8 +1691,13 @@ public sealed class ChatLogWindow : Window
|
||||
// Custom styles can have cellPadding that go above 4, which GetScrollY isn't respecting
|
||||
var cellPaddingOffset =
|
||||
!compact && oldCellPadding.Y > 4f ? oldCellPadding.Y - 4f : 0f;
|
||||
if (switchedTab || ImGui.GetScrollY() + cellPaddingOffset >= ImGui.GetScrollMaxY())
|
||||
if (
|
||||
switchedTab
|
||||
|| _scrollToBottomRequested
|
||||
|| ImGui.GetScrollY() + cellPaddingOffset >= ImGui.GetScrollMaxY()
|
||||
)
|
||||
ImGui.SetScrollHereY(1f);
|
||||
_scrollToBottomRequested = false;
|
||||
|
||||
handler.Draw();
|
||||
}
|
||||
@@ -1505,15 +1733,19 @@ public sealed class ChatLogWindow : Window
|
||||
var maxLines = Plugin.Config.MaxLinesToRender;
|
||||
var startLine = messages.Count > maxLines ? messages.Count - maxLines : 0;
|
||||
|
||||
// Card-mode pre-loop: theme/drawList/winLeft/winRight/border are invariant
|
||||
// per DrawMessages call; only cursorY moves per row.
|
||||
// Card-mode pre-loop: theme/drawList/winLeft/winRight are
|
||||
// invariant per DrawMessages call. borderColorAbgr used to be
|
||||
// hoisted here too, but PM-3d (v1.5.4) modulates it by
|
||||
// tab._cardHoverAlpha per row, so it moves into the AddLine
|
||||
// call below. anyCardHovered aggregates the row-hover state
|
||||
// across all card-rows; the lerp runs once at the loop end so
|
||||
// the next frame paints with the updated alpha.
|
||||
var theme = Plugin.ThemeRegistry.Active;
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
var winLeft = ImGui.GetWindowPos().X;
|
||||
var winRight = winLeft + ImGui.GetWindowSize().X;
|
||||
var borderColorAbgr = ColourUtil.RgbaToAbgr(
|
||||
(theme.Colors.Border & 0xFFFFFF00u) | 0x33u
|
||||
);
|
||||
var baseBorderRgba = (theme.Colors.Border & 0xFFFFFF00u) | 0x33u;
|
||||
var anyCardHovered = false;
|
||||
|
||||
for (var i = startLine; i < messages.Count; i++)
|
||||
{
|
||||
@@ -1669,6 +1901,8 @@ public sealed class ChatLogWindow : Window
|
||||
var useCard = !Plugin.Config.UseCompactDensity;
|
||||
if (useCard)
|
||||
{
|
||||
var rowStartY = ImGui.GetCursorScreenPos().Y;
|
||||
|
||||
if (message.Sender.Count > 0)
|
||||
{
|
||||
var senderColor =
|
||||
@@ -1692,9 +1926,18 @@ public sealed class ChatLogWindow : Window
|
||||
else
|
||||
DrawChunks(message.Content, true, handler, lineWidth);
|
||||
|
||||
// Border bottom as card separator. Alpha reduced to 0x33 for subtlety.
|
||||
// Border bottom as card separator. Base alpha 0x33;
|
||||
// PM-3d lifts it by up to ~+0x70 while any row in this
|
||||
// tab is hovered. _cardHoverAlpha lerps at the loop
|
||||
// end, so the one-frame lag is invisible at 10f speed.
|
||||
{
|
||||
var rowEndY = ImGui.GetCursorScreenPos().Y;
|
||||
var hoverBoost = 0.45f * tab._cardHoverAlpha;
|
||||
var alphaByte = (uint)
|
||||
Math.Clamp((int)(0x33u + hoverBoost * 255f), 0x33, 0xCC);
|
||||
var borderColorAbgr = ColourUtil.RgbaToAbgr(
|
||||
(baseBorderRgba & 0xFFFFFF00u) | alphaByte
|
||||
);
|
||||
drawList.AddLine(
|
||||
new Vector2(winLeft + 4, rowEndY - 1),
|
||||
new Vector2(winRight - 4, rowEndY - 1),
|
||||
@@ -1702,6 +1945,17 @@ public sealed class ChatLogWindow : Window
|
||||
1f
|
||||
);
|
||||
ImGui.Dummy(new Vector2(0, 2));
|
||||
|
||||
// Whole-row hover test. IsItemHovered would only see
|
||||
// the 2px Dummy above, so hit-test the row rect from
|
||||
// its start Y down to the separator line instead.
|
||||
if (
|
||||
ImGui.IsMouseHoveringRect(
|
||||
new Vector2(winLeft, rowStartY),
|
||||
new Vector2(winRight, rowEndY)
|
||||
)
|
||||
)
|
||||
anyCardHovered = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1726,6 +1980,20 @@ public sealed class ChatLogWindow : Window
|
||||
|
||||
message.IsVisible[tab.Identifier] = ImGui.IsItemVisible();
|
||||
}
|
||||
|
||||
// PM-3d: update the per-tab card-hover lerp once per
|
||||
// DrawMessages call. ReduceMotion snaps to the target;
|
||||
// otherwise the border alpha eases toward it over a few
|
||||
// frames the next time the rows paint.
|
||||
var cardTarget = anyCardHovered ? 1f : 0f;
|
||||
tab._cardHoverAlpha = Plugin.Config.ReduceMotion
|
||||
? cardTarget
|
||||
: FrameLerp.Smooth(
|
||||
tab._cardHoverAlpha,
|
||||
cardTarget,
|
||||
speed: 10f,
|
||||
deltaTime: ImGui.GetIO().DeltaTime
|
||||
);
|
||||
}
|
||||
catch (ApplicationException)
|
||||
{
|
||||
@@ -1976,7 +2244,19 @@ public sealed class ChatLogWindow : Window
|
||||
ColourUtil.RgbaToAbgr(theme.Colors.Surface)
|
||||
)
|
||||
)
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(iconColor)))
|
||||
// PM-3c: icon alpha eases from 40% (dim) to 100% on
|
||||
// hover. _hoverAlpha lerps at the end of this block,
|
||||
// so the colour for frame N uses frame N-1's value --
|
||||
// a sub-frame lag that is invisible at 10f speed.
|
||||
using (
|
||||
ImRaii.PushColor(
|
||||
ImGuiCol.Text,
|
||||
ColourUtil.ApplyAlpha(
|
||||
ColourUtil.RgbaToAbgr(iconColor),
|
||||
0.4f + 0.6f * tab._hoverAlpha
|
||||
)
|
||||
)
|
||||
)
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
{
|
||||
// Button stretches with the configured sidebar width so a
|
||||
@@ -1988,6 +2268,19 @@ public sealed class ChatLogWindow : Window
|
||||
);
|
||||
}
|
||||
|
||||
// PM-3c hover-lerp: ramp _hoverAlpha toward 1 while the
|
||||
// icon button is hovered, back to 0 otherwise.
|
||||
// ReduceMotion snaps so the dim/full states stay binary.
|
||||
var hoverTarget = ImGui.IsItemHovered() ? 1f : 0f;
|
||||
tab._hoverAlpha = Plugin.Config.ReduceMotion
|
||||
? hoverTarget
|
||||
: FrameLerp.Smooth(
|
||||
tab._hoverAlpha,
|
||||
hoverTarget,
|
||||
speed: 10f,
|
||||
deltaTime: ImGui.GetIO().DeltaTime
|
||||
);
|
||||
|
||||
if (isCurrentTab)
|
||||
{
|
||||
// Vertical accent pill on the left window edge, 3px wide, half tab height,
|
||||
@@ -2114,14 +2407,50 @@ public sealed class ChatLogWindow : Window
|
||||
Plugin.WantedTab = null;
|
||||
}
|
||||
|
||||
// DrawChatHeaderToolbar: renders the pop-out button for the active tab.
|
||||
// v1.3.0 also renders the optional Honorific title slot left of it.
|
||||
// DrawChatHeaderToolbar: renders the honorific title slot, the optional
|
||||
// scroll-to-bottom button, and the pop-out button for the active tab.
|
||||
private void DrawChatHeaderToolbar(Tab tab)
|
||||
{
|
||||
DrawHonorificTitleSlot();
|
||||
DrawScrollToBottomToolbarButton();
|
||||
DrawPopOutButton(tab);
|
||||
}
|
||||
|
||||
// Draws an arrow-down button in the toolbar when the user has scrolled up
|
||||
// from the live end of the chat log. Clicking it requests a snap to bottom.
|
||||
//
|
||||
// _childScrolledUp is set at the end of DrawMessageLog, which runs AFTER
|
||||
// DrawChatHeaderToolbar in the same frame. So this button always reflects the
|
||||
// previous frame's scroll state, a one-frame lag that is imperceptible in use.
|
||||
//
|
||||
// Both this button and DrawPopOutButton use SetCursorPosX with absolute
|
||||
// positioning (cursorX + GetContentRegionAvail().X - N * iconWidth). Because
|
||||
// each call computes its own target X from the right edge, they are independent
|
||||
// of each other and of what the cursor position happens to be at call time.
|
||||
// The pop-out button lands at rightEdge - iconWidth regardless of call order.
|
||||
private void DrawScrollToBottomToolbarButton()
|
||||
{
|
||||
if (!_childScrolledUp)
|
||||
return;
|
||||
|
||||
var avail = ImGui.GetContentRegionAvail().X;
|
||||
var iconWidth = ImGui.GetFrameHeight();
|
||||
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + avail - 2 * iconWidth - spacing);
|
||||
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.ArrowDown,
|
||||
tooltip: HellionStrings.ChatLog_ScrollToBottom_Tooltip
|
||||
)
|
||||
)
|
||||
_scrollToBottomRequested = true;
|
||||
|
||||
// Keep the pop-out button on the same toolbar row. Without this the
|
||||
// button item ends the line and the pop-out drops to the next row.
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
private void DrawPopOutButton(Tab tab)
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail().X;
|
||||
@@ -2173,7 +2502,13 @@ public sealed class ChatLogWindow : Window
|
||||
crownWidth = ImGui.CalcTextSize(FontAwesomeIcon.Crown.ToIconString()).X;
|
||||
}
|
||||
|
||||
var maxTitleWidth = avail - iconWidth - gapBeforeButton - crownWidth - gapAfterCrown;
|
||||
// When the scroll button is also present it occupies iconWidth + ItemSpacing.X
|
||||
// to the left of the pop-out button, so shrink the title budget accordingly.
|
||||
var scrollButtonReserve = _childScrolledUp
|
||||
? iconWidth + ImGui.GetStyle().ItemSpacing.X
|
||||
: 0f;
|
||||
var maxTitleWidth =
|
||||
avail - iconWidth - scrollButtonReserve - gapBeforeButton - crownWidth - gapAfterCrown;
|
||||
if (maxTitleWidth <= 0)
|
||||
{
|
||||
return;
|
||||
@@ -2299,8 +2634,13 @@ public sealed class ChatLogWindow : Window
|
||||
var anyChanged = false;
|
||||
var tabs = Plugin.Config.Tabs;
|
||||
|
||||
// Focus the rename field on the frame the context menu opens so the
|
||||
// user can type immediately. Buffer raised 128 -> 512 to match the
|
||||
// settings-tab rename (Ui/SettingsTabs/Tabs.cs). One name limit, not two.
|
||||
if (ImGui.IsWindowAppearing())
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
ImGui.SetNextItemWidth(250f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputText("##tab-name", ref tab.Name, 128))
|
||||
if (ImGui.InputText("##tab-name", ref tab.Name, 512))
|
||||
anyChanged = true;
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.TrashAlt, tooltip: Language.ChatLog_Tabs_Delete))
|
||||
@@ -2741,6 +3081,14 @@ public sealed class ChatLogWindow : Window
|
||||
float lineWidth = 0f
|
||||
)
|
||||
{
|
||||
// UI-7: render a copy with the sender name reformatted per the user's
|
||||
// display options. Skipped in screenshot mode so the name-anonymising
|
||||
// path in DrawChunk stays reliable (privacy wins). ForDisplay returns
|
||||
// the list unchanged when nothing applies, so non-sender lists and the
|
||||
// neutral default cost only a quick scan.
|
||||
if (!ScreenshotMode)
|
||||
chunks = SenderNameDisplay.ForDisplay(chunks);
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
|
||||
for (var i = 0; i < chunks.Count; i++)
|
||||
|
||||
@@ -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,41 @@ 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. The card
|
||||
// behind the image gives the dark fox enough contrast against the
|
||||
// plugin's dark UI so the logo reads clearly at a glance.
|
||||
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);
|
||||
}
|
||||
}
|
||||
const uint CardColor = 0xFFE8E8E8; // off-white fill so the dark fox pops
|
||||
var imgHeight = 170f * ImGuiHelpers.GlobalScale;
|
||||
var imgWidth = imgHeight * banner.Size.X / banner.Size.Y;
|
||||
var pad = 14f * ImGuiHelpers.GlobalScale;
|
||||
var cardWidth = imgWidth + pad * 2f;
|
||||
var cardHeight = imgHeight + pad * 2f;
|
||||
var rounding = 8f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Centre the card in the content region. Clamp to zero so the card
|
||||
// never shifts left of the window edge on very narrow windows.
|
||||
var offsetX = Math.Max(0f, (ImGui.GetContentRegionAvail().X - cardWidth) * 0.5f);
|
||||
var cardOrigin = ImGui.GetCursorScreenPos() + new Vector2(offsetX, 0f);
|
||||
|
||||
// Draw the rounded card behind the image, then place the image on top.
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
cardOrigin,
|
||||
cardOrigin + new Vector2(cardWidth, cardHeight),
|
||||
CardColor,
|
||||
rounding
|
||||
);
|
||||
ImGui.SetCursorScreenPos(cardOrigin + new Vector2(pad, pad));
|
||||
ImGui.Image(banner.Handle, new Vector2(imgWidth, imgHeight));
|
||||
|
||||
// Advance the layout cursor past the full card so the content below
|
||||
// starts at the right position and does not overlap the card.
|
||||
ImGui.SetCursorScreenPos(cardOrigin);
|
||||
ImGui.Dummy(new Vector2(cardWidth, cardHeight));
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
@@ -347,10 +365,10 @@ public sealed class FirstRunWizard : Window
|
||||
if (ImGui.Checkbox(HellionStrings.Wizard_Step3_LoadPreviousSession_Label, ref loadPrev))
|
||||
{
|
||||
_state.PendingLoadPreviousSession = loadPrev;
|
||||
// Mirror the DataManagement coupling: turning load-previous on
|
||||
// Mirror the DataAndPrivacy coupling: turning load-previous on
|
||||
// also turns filter-include on (otherwise old messages bypass
|
||||
// the filter chain), and turning filter-include off forces
|
||||
// load-previous off. Same idiom as Ui/SettingsTabs/DataManagement.cs:182-200.
|
||||
// load-previous off. Same idiom as Ui/SettingsTabs/DataAndPrivacy.cs.
|
||||
if (loadPrev)
|
||||
_state.PendingFilterIncludePreviousSessions = true;
|
||||
}
|
||||
|
||||
@@ -33,11 +33,31 @@ internal static class HellionStyle
|
||||
|
||||
// Global color and style stack pushed once per frame.
|
||||
// windowOpacity: window background alpha (0.5-1.0).
|
||||
internal static IDisposable PushGlobal(Theme theme, float windowOpacity = 1.0f)
|
||||
internal static IDisposable PushGlobal(
|
||||
Theme theme,
|
||||
ThemeRegistry registry,
|
||||
float windowOpacity = 1.0f
|
||||
)
|
||||
{
|
||||
var c = theme.Colors;
|
||||
var l = theme.Layout;
|
||||
var a = theme.AbgrCache;
|
||||
|
||||
// Crossfade: PM-1 reads a lerped snapshot during the 300ms window
|
||||
// following a Switch (TryGetActiveCrossfade returns false outside
|
||||
// the window or while ReduceMotion is on). Only the ABGR-slot path
|
||||
// crossfades -- WindowBg/ChildBg RGBA stays bound to the user's
|
||||
// per-window opacity override and must not fade. See
|
||||
// feedback_dalamud_pinning_override.
|
||||
ThemeAbgrCache a;
|
||||
if (!Plugin.Config.ReduceMotion && registry.TryGetActiveCrossfade(out var lerped))
|
||||
{
|
||||
a = lerped;
|
||||
}
|
||||
else
|
||||
{
|
||||
a = theme.AbgrCache;
|
||||
}
|
||||
|
||||
var stack = new StackHandle();
|
||||
|
||||
var alphaByte = (uint)Math.Clamp((int)(windowOpacity * 255f), 0x55, 0xFF);
|
||||
|
||||
@@ -118,7 +118,7 @@ internal class Popout : Window
|
||||
|
||||
var handler = ChatLogWindow.HandlerLender.Borrow();
|
||||
var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight - hintBannerHeight;
|
||||
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false);
|
||||
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false, updateScrollState: false);
|
||||
|
||||
if (inputEnabled && InputBar != null)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,10 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
private List<ISettingsTab> Tabs { get; }
|
||||
private int CurrentTab;
|
||||
private SettingsView View = SettingsView.Overview;
|
||||
|
||||
// Set when a section is freshly entered; the first Draw afterwards reads it
|
||||
// and clears it, so each section starts collapsed every time it is opened.
|
||||
private bool _sectionJustEntered;
|
||||
private readonly SettingsOverview Overview;
|
||||
|
||||
internal SettingsWindow(Plugin plugin, ILoggerFactory loggerFactory)
|
||||
@@ -46,15 +50,12 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
Tabs =
|
||||
[
|
||||
new General(Plugin, Mutable),
|
||||
new ThemeAndLayout(Plugin, Mutable, loggerFactory.CreateLogger<ThemeAndLayout>()),
|
||||
new FontsAndColours(Plugin, Mutable, loggerFactory.CreateLogger<FontsAndColours>()),
|
||||
new SettingsTabs.Window(Plugin, Mutable),
|
||||
new Appearance(Plugin, Mutable, loggerFactory.CreateLogger<Appearance>()),
|
||||
new Chat(Plugin, Mutable),
|
||||
new SettingsTabs.Window(Plugin, Mutable),
|
||||
new SettingsTabs.Tabs(Plugin, Mutable),
|
||||
new SettingsTabs.Privacy(Plugin, Mutable),
|
||||
new DataManagement(Plugin, Mutable, loggerFactory.CreateLogger<DataManagement>()),
|
||||
new SettingsTabs.Integrations(Plugin, Mutable),
|
||||
new Information(Mutable),
|
||||
new DataAndPrivacy(Plugin, Mutable, loggerFactory.CreateLogger<DataAndPrivacy>()),
|
||||
new About(Plugin, Mutable),
|
||||
];
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
@@ -106,6 +107,7 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
{
|
||||
CurrentTab = tabIndex;
|
||||
View = SettingsView.Detail;
|
||||
_sectionJustEntered = true;
|
||||
}
|
||||
|
||||
internal void OpenOverview()
|
||||
@@ -148,7 +150,10 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
|
||||
using var child = ImRaii.Child("##chat2-settings-detail", new Vector2(-1, height));
|
||||
if (child.Success)
|
||||
Tabs[CurrentTab].Draw(false);
|
||||
{
|
||||
Tabs[CurrentTab].Draw(_sectionJustEntered);
|
||||
_sectionJustEntered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSaveButtons()
|
||||
@@ -201,6 +206,18 @@ public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
|
||||
var hideChanged = !Mutable.HideChat && Mutable.HideChat != Plugin.Config.HideChat;
|
||||
var languageChanged = Mutable.LanguageOverride != Plugin.Config.LanguageOverride;
|
||||
|
||||
// v1.5.3: Auto-enable the ExtraGlyphRanges flag matching the new
|
||||
// locale so non-Latin scripts render immediately. Without this,
|
||||
// a user switching to Korean would see "===" until they manually
|
||||
// tick the Korean range in Fonts & Colours.
|
||||
if (languageChanged)
|
||||
{
|
||||
var required = Mutable.LanguageOverride.RequiredGlyphRanges();
|
||||
if (required != 0)
|
||||
Mutable.ExtraGlyphRanges |= required;
|
||||
}
|
||||
|
||||
var fontChanged =
|
||||
Mutable.GlobalFontV2 != Plugin.Config.GlobalFontV2
|
||||
|| Mutable.JapaneseFontV2 != Plugin.Config.JapaneseFontV2
|
||||
|
||||
@@ -21,44 +21,29 @@ internal sealed class SettingsOverview
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.Palette,
|
||||
HellionStrings.Settings_Card_ThemeAndLayout_Title,
|
||||
HellionStrings.Settings_Card_ThemeAndLayout_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.Font,
|
||||
HellionStrings.Settings_Card_FontsAndColours_Title,
|
||||
HellionStrings.Settings_Card_FontsAndColours_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.WindowMaximize,
|
||||
HellionStrings.Settings_Card_Window_Title,
|
||||
HellionStrings.Settings_Card_Window_Subtext
|
||||
HellionStrings.Settings_Card_Appearance_Title,
|
||||
HellionStrings.Settings_Card_Appearance_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.Comments,
|
||||
HellionStrings.Settings_Card_Chat_Title,
|
||||
HellionStrings.Settings_Card_Chat_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.WindowMaximize,
|
||||
HellionStrings.Settings_Card_Window_Title,
|
||||
HellionStrings.Settings_Card_Window_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.FolderTree,
|
||||
HellionStrings.Settings_Card_Tabs_Title,
|
||||
HellionStrings.Settings_Card_Tabs_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.ShieldAlt,
|
||||
HellionStrings.Settings_Card_Privacy_Title,
|
||||
HellionStrings.Settings_Card_Privacy_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.Database,
|
||||
HellionStrings.Settings_Card_DataManagement_Title,
|
||||
HellionStrings.Settings_Card_DataManagement_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.Plug,
|
||||
HellionStrings.Settings_Card_Integrations_Title,
|
||||
HellionStrings.Settings_Card_Integrations_Subtext
|
||||
),
|
||||
(
|
||||
FontAwesomeIcon.InfoCircle,
|
||||
HellionStrings.Settings_Card_Information_Title,
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
using System.Numerics;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Branding;
|
||||
using HellionChat.Integrations;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
// The About tab absorbs the former Integrations tab (now the first section)
|
||||
// and organises its remaining content into four thematic sections.
|
||||
internal sealed class About : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Information + "###tabs-information";
|
||||
|
||||
private readonly List<string> Translators =
|
||||
[
|
||||
"q673135110",
|
||||
"Akizem",
|
||||
"d0tiKs",
|
||||
"Moonlight_Everlit",
|
||||
"Dark32",
|
||||
"andreycout",
|
||||
"Button_",
|
||||
"Cali666",
|
||||
"cassandra308",
|
||||
"lokinmodar",
|
||||
"jtabox",
|
||||
"AkiraYorumoto",
|
||||
"MKhayle",
|
||||
"elena.space",
|
||||
"imlisa",
|
||||
"andrei5125",
|
||||
"ShivaMaheshvara",
|
||||
"aislinn87",
|
||||
"nishinatsu051",
|
||||
"lichuyuan",
|
||||
"Risu64",
|
||||
"yummypillow",
|
||||
"witchymary",
|
||||
"Yuzumi",
|
||||
"zomsakura",
|
||||
"Sirayuki",
|
||||
];
|
||||
|
||||
internal About(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
Translators.Sort(
|
||||
(a, b) =>
|
||||
string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal)
|
||||
);
|
||||
}
|
||||
|
||||
public void Draw(bool sectionJustEntered)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
DrawExtensionsSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawPluginInfoSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawProjectSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawTranslatorsSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawChangelogSection(sectionJustEntered);
|
||||
}
|
||||
|
||||
// ── Extensions ──────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawExtensionsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Extensions);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextWrapped(HellionStrings.Settings_Integrations_Intro);
|
||||
ImGui.Spacing();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawHonorificSection();
|
||||
ImGui.Spacing();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawComingSoonSection();
|
||||
ImGui.Spacing();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawGotAnIdeaSection();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHonorificSection()
|
||||
{
|
||||
DrawSectionHeader(HellionStrings.Settings_Integrations_Honorific_SectionHeader);
|
||||
|
||||
DrawHonorificStatus();
|
||||
ImGui.Spacing();
|
||||
|
||||
// Toggle works regardless of detection state: "show when available,
|
||||
// hide otherwise". Disabling it when Honorific is missing would force
|
||||
// the user to retoggle on every reload.
|
||||
if (
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Integrations_Honorific_Toggle,
|
||||
ref Mutable.ShowHonorificTitleInHeader
|
||||
)
|
||||
)
|
||||
{
|
||||
Plugin.SaveConfig();
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
using (
|
||||
ImRaii.PushColor(
|
||||
ImGuiCol.Text,
|
||||
ColourUtil.RgbaToAbgr(Plugin.ThemeRegistry.Active.Colors.TextMuted)
|
||||
)
|
||||
)
|
||||
{
|
||||
ImGui.TextWrapped(HellionStrings.Settings_Integrations_Honorific_ToggleHint);
|
||||
}
|
||||
|
||||
if (
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Integrations_Honorific_Glow_Toggle,
|
||||
ref Mutable.ShowHonorificGlow
|
||||
)
|
||||
)
|
||||
{
|
||||
Plugin.SaveConfig();
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Integrations_Honorific_Glow_Hint);
|
||||
}
|
||||
|
||||
// Honorific has no LICENSE in its repo so we link upstream and author
|
||||
// instead of bundling assets. Text labels because FA Brands isn't
|
||||
// guaranteed in Dalamud's font set.
|
||||
ImGui.Spacing();
|
||||
if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkRepo))
|
||||
{
|
||||
Plugin.PlatformUtil.OpenLink(IntegrationLinks.HonorificRepo);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(HellionStrings.Settings_Integrations_Honorific_LinkAuthor))
|
||||
{
|
||||
Plugin.PlatformUtil.OpenLink(IntegrationLinks.HonorificAuthor);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHonorificStatus()
|
||||
{
|
||||
var theme = Plugin.ThemeRegistry.Active;
|
||||
var service = Plugin.HonorificService;
|
||||
|
||||
if (service.IsAvailable && service.DetectedApiVersion is { } version)
|
||||
{
|
||||
DrawStatusGlyph('●', theme.Colors.StatusSuccess);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(
|
||||
string.Format(
|
||||
HellionStrings.Settings_Integrations_Honorific_Status_Detected,
|
||||
version.Major,
|
||||
version.Minor
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (service.DetectedApiVersion is { } incompatibleVersion)
|
||||
{
|
||||
DrawStatusGlyph('⚠', theme.Colors.StatusWarning);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(
|
||||
string.Format(
|
||||
HellionStrings.Settings_Integrations_Honorific_Status_Incompatible,
|
||||
HonorificService.ExpectedApiMajor,
|
||||
incompatibleVersion.Major,
|
||||
incompatibleVersion.Minor
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawStatusGlyph('○', theme.Colors.TextMuted);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(
|
||||
HellionStrings.Settings_Integrations_Honorific_Status_NotInstalled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawStatusGlyph(char glyph, uint rgba)
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(rgba)))
|
||||
{
|
||||
ImGui.TextUnformatted(glyph.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawComingSoonSection()
|
||||
{
|
||||
DrawSectionHeader(HellionStrings.Settings_Integrations_ComingSoon_SectionHeader);
|
||||
ImGui.TextWrapped(HellionStrings.Settings_Integrations_ComingSoon_Intro);
|
||||
ImGui.Spacing();
|
||||
|
||||
// Each integration cycle removes its stub here and adds a full section above.
|
||||
DrawComingSoonItem(
|
||||
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Title,
|
||||
HellionStrings.Settings_Integrations_ComingSoon_ContextMenu_Description
|
||||
);
|
||||
DrawComingSoonItem(
|
||||
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Title,
|
||||
HellionStrings.Settings_Integrations_ComingSoon_Notifications_Description
|
||||
);
|
||||
DrawComingSoonItem(
|
||||
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Title,
|
||||
HellionStrings.Settings_Integrations_ComingSoon_RPStatus_Description
|
||||
);
|
||||
DrawComingSoonItem(
|
||||
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Title,
|
||||
HellionStrings.Settings_Integrations_ComingSoon_ExtraChat_Description
|
||||
);
|
||||
DrawComingSoonItem(
|
||||
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Title,
|
||||
HellionStrings.Settings_Integrations_ComingSoon_QuickDM_Description
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawComingSoonItem(string title, string description)
|
||||
{
|
||||
var theme = Plugin.ThemeRegistry.Active;
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted)))
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
{
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.Hourglass.ToIconString());
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(title);
|
||||
using (ImRaii.PushIndent())
|
||||
{
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.TextMuted)))
|
||||
{
|
||||
ImGui.TextWrapped(description);
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
private void DrawGotAnIdeaSection()
|
||||
{
|
||||
DrawSectionHeader(HellionStrings.Settings_Integrations_GotAnIdea_SectionHeader);
|
||||
ImGui.TextWrapped(HellionStrings.Settings_Integrations_GotAnIdea_Body);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button(HellionStrings.Settings_Integrations_GotAnIdea_LinkLabel))
|
||||
{
|
||||
Plugin.PlatformUtil.OpenLink(BrandingLinks.HellionForgeDiscordInvite);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSectionHeader(string label)
|
||||
{
|
||||
var theme = Plugin.ThemeRegistry.Active;
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ColourUtil.RgbaToAbgr(theme.Colors.Primary)))
|
||||
{
|
||||
ImGui.TextUnformatted("── " + label + " ──");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Plugin info ──────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawPluginInfoSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_PluginInfo);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
DrawFoxBanner();
|
||||
ImGuiHelpers.ScaledDummy(6.0f);
|
||||
|
||||
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Authors);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Discord);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama");
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Version);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(
|
||||
ImGuiColors.ParsedOrange,
|
||||
Plugin.Interface.Manifest.AssemblyVersion.ToString(3)
|
||||
);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||
Plugin.PlatformUtil.OpenLink(
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFoxBanner()
|
||||
{
|
||||
var banner = FoxBannerTexture.Shared.GetWrapOrDefault();
|
||||
if (banner is null)
|
||||
return;
|
||||
|
||||
const uint CardColor = 0xFFE8E8E8; // off-white fill so the dark fox pops
|
||||
var imgHeight = 170f * ImGuiHelpers.GlobalScale;
|
||||
var imgWidth = imgHeight * banner.Size.X / banner.Size.Y;
|
||||
var pad = 14f * ImGuiHelpers.GlobalScale;
|
||||
var cardWidth = imgWidth + pad * 2f;
|
||||
var cardHeight = imgHeight + pad * 2f;
|
||||
var rounding = 8f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Left-aligned: card origin stays at the current layout cursor position.
|
||||
var cardOrigin = ImGui.GetCursorScreenPos();
|
||||
|
||||
// Draw the rounded card behind the image, then place the image on top.
|
||||
ImGui
|
||||
.GetWindowDrawList()
|
||||
.AddRectFilled(
|
||||
cardOrigin,
|
||||
cardOrigin + new Vector2(cardWidth, cardHeight),
|
||||
CardColor,
|
||||
rounding
|
||||
);
|
||||
ImGui.SetCursorScreenPos(cardOrigin + new Vector2(pad, pad));
|
||||
ImGui.Image(banner.Handle, new Vector2(imgWidth, imgHeight));
|
||||
|
||||
// Advance the layout cursor past the full card so content below does not overlap.
|
||||
ImGui.SetCursorScreenPos(cardOrigin);
|
||||
ImGui.Dummy(new Vector2(cardWidth, cardHeight));
|
||||
}
|
||||
|
||||
// ── The Project ──────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawProjectSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Project);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia"))
|
||||
Plugin.PlatformUtil.OpenLink("https://hellion-media.de");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream"))
|
||||
Plugin.PlatformUtil.OpenLink("https://github.com/Infiziert90/ChatTwo");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P2);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P2);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Translators ──────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawTranslatorsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Translators);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// The translator list belongs to the Chat 2 upstream Crowdin project.
|
||||
using var translatorTree = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode);
|
||||
if (translatorTree)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false);
|
||||
foreach (var translator in Translators)
|
||||
ImGui.TextUnformatted(translator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Changelog ────────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawChangelogSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Changelog);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PrintChangelog_Name, ref Mutable.PrintChangelog);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PrintChangelog_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var changelog = Plugin.Interface.Manifest.Changelog;
|
||||
if (changelog == null)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Changelog_Header);
|
||||
ImGui.TextUnformatted(
|
||||
$"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}"
|
||||
);
|
||||
ImGui.Spacing();
|
||||
foreach (var sentence in changelog.Split("\n"))
|
||||
{
|
||||
if (sentence == string.Empty)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
var indented = sentence.StartsWith('-') || sentence.StartsWith(" -");
|
||||
using var indent = ImRaii.PushIndent(10.0f, true, indented);
|
||||
ImGui.TextUnformatted(sentence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
using System.Numerics;
|
||||
using Dalamud;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Themes;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Appearance : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<Appearance> _logger;
|
||||
|
||||
private string? _applyDismissedFor;
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Appearance + "###tabs-appearance";
|
||||
|
||||
internal Appearance(Plugin plugin, Configuration mutable, ILogger<Appearance> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool sectionJustEntered)
|
||||
{
|
||||
DrawThemeSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawFontsSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawColoursSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawWindowStyleSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawTimestampSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawAnimationsSection(sectionJustEntered);
|
||||
}
|
||||
|
||||
// ── Theme ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawThemeSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Theme);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
var registry = Plugin.ThemeRegistry;
|
||||
var active = registry.Get(Mutable.Theme);
|
||||
|
||||
ImGui.TextUnformatted(
|
||||
string.Format(HellionStrings.Settings_Themes_Active, active.Name)
|
||||
);
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF8FA3B5u))
|
||||
ImGui.TextUnformatted(active.Author);
|
||||
|
||||
DrawChatColorsApplyBanner(active);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(HellionStrings.Settings_Themes_BuiltIns);
|
||||
ImGui.Spacing();
|
||||
DrawThemeGrid(registry.AllBuiltIns(), active.Slug);
|
||||
|
||||
var customs = registry.AllCustom().ToList();
|
||||
if (customs.Count > 0)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.Settings_Themes_Custom);
|
||||
ImGui.Spacing();
|
||||
DrawThemeGrid(customs, active.Slug);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button(HellionStrings.Settings_Themes_OpenFolder))
|
||||
{
|
||||
var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(dir);
|
||||
Plugin.PlatformUtil.OpenLink(dir);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(HellionStrings.Settings_Themes_ExportActive))
|
||||
{
|
||||
var dir = Path.Combine(Plugin.Interface.ConfigDirectory.FullName, "themes");
|
||||
Directory.CreateDirectory(dir);
|
||||
var fileName = $"{active.Slug}.export.json";
|
||||
var path = Path.Combine(dir, fileName);
|
||||
var json = ThemeJsonWriter.Serialize(active);
|
||||
File.WriteAllText(path, json);
|
||||
_logger.LogInformation($"Exported active theme '{active.Slug}' to {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeGrid(IEnumerable<Theme> themes, string activeSlug)
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var columns = avail.X >= 700f ? 3 : 2;
|
||||
var cardWidth = (avail.X - (columns - 1) * 8f) / columns;
|
||||
var cardHeight = 140f;
|
||||
|
||||
var list = themes.ToList();
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
DrawThemeCard(list[i], activeSlug, cardWidth, cardHeight);
|
||||
|
||||
if ((i + 1) % columns != 0 && i != list.Count - 1)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThemeCard(Theme theme, string activeSlug, float w, float h)
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
|
||||
var isActive = string.Equals(theme.Slug, activeSlug, StringComparison.OrdinalIgnoreCase);
|
||||
var cursorBefore = ImGui.GetCursorScreenPos();
|
||||
var clicked = ImGui.InvisibleButton($"##theme-card-{theme.Slug}", new Vector2(w, h));
|
||||
var hovered = ImGui.IsItemHovered();
|
||||
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
var bg = ColourUtil.RgbaToAbgr(theme.Colors.WindowBg | 0xFFu);
|
||||
draw.AddRectFilled(cursorBefore, cursorBefore + new Vector2(w, h), bg, 4f);
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
var border = ColourUtil.RgbaToAbgr(theme.Colors.Primary);
|
||||
draw.AddRect(
|
||||
cursorBefore,
|
||||
cursorBefore + new Vector2(w, h),
|
||||
border,
|
||||
4f,
|
||||
ImDrawFlags.None,
|
||||
2f
|
||||
);
|
||||
}
|
||||
else if (hovered)
|
||||
{
|
||||
var border = ColourUtil.RgbaToAbgr(theme.Colors.PrimaryLight & 0xFFFFFF99u);
|
||||
draw.AddRect(
|
||||
cursorBefore,
|
||||
cursorBefore + new Vector2(w, h),
|
||||
border,
|
||||
4f,
|
||||
ImDrawFlags.None,
|
||||
1f
|
||||
);
|
||||
}
|
||||
|
||||
var mockupOrigin = cursorBefore + new Vector2(12f, 12f);
|
||||
var mockupSize = new Vector2(w - 24f, 60f);
|
||||
ThemeMockup.Draw(mockupOrigin, mockupSize, theme);
|
||||
|
||||
var textColor = ColourUtil.RgbaToAbgr(theme.Colors.TextPrimary);
|
||||
var mutedColor = ColourUtil.RgbaToAbgr(theme.Colors.TextMuted);
|
||||
draw.AddText(cursorBefore + new Vector2(12f, 80f), textColor, theme.Name);
|
||||
draw.AddText(cursorBefore + new Vector2(12f, 100f), mutedColor, theme.Author);
|
||||
|
||||
ImGui.EndGroup();
|
||||
|
||||
if (clicked)
|
||||
{
|
||||
Mutable.Theme = theme.Slug;
|
||||
Plugin.ThemeRegistry.Switch(theme.Slug);
|
||||
_applyDismissedFor = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChatColorsApplyBanner(Theme active)
|
||||
{
|
||||
if (active.ChatColors is not { Channels.Count: > 0 } themeChatColors)
|
||||
return;
|
||||
|
||||
if (_applyDismissedFor == active.Slug)
|
||||
return;
|
||||
|
||||
var alreadyMatching = themeChatColors.Channels.All(kvp =>
|
||||
Mutable.ChatColours.TryGetValue(kvp.Key, out var current) && current == kvp.Value
|
||||
);
|
||||
if (alreadyMatching)
|
||||
return;
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
var border = ColourUtil.RgbaToAbgr(active.Colors.Primary);
|
||||
var bgFill = ColourUtil.RgbaToAbgr((active.Colors.Surface & 0xFFFFFF00u) | 0xCCu);
|
||||
var origin = ImGui.GetCursorScreenPos();
|
||||
var width = ImGui.GetContentRegionAvail().X;
|
||||
var height = 64f;
|
||||
var draw = ImGui.GetWindowDrawList();
|
||||
draw.AddRectFilled(origin, origin + new Vector2(width, height), bgFill, 4f);
|
||||
draw.AddRect(origin, origin + new Vector2(width, height), border, 4f, ImDrawFlags.None, 1f);
|
||||
|
||||
var textColor = ColourUtil.RgbaToAbgr(active.Colors.TextPrimary);
|
||||
draw.AddText(
|
||||
origin + new Vector2(12f, 10f),
|
||||
textColor,
|
||||
HellionStrings.Settings_Themes_ApplyChatColors_Hint
|
||||
);
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, active.Colors.Primary))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, active.Colors.PrimaryLight))
|
||||
using (ImRaii.PushColor(ImGuiCol.ButtonActive, active.Colors.PrimaryDark))
|
||||
{
|
||||
ImGui.SetCursorScreenPos(origin + new Vector2(12f, 32f));
|
||||
if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Apply))
|
||||
{
|
||||
foreach (var kvp in themeChatColors.Channels)
|
||||
Mutable.ChatColours[kvp.Key] = kvp.Value;
|
||||
_applyDismissedFor = active.Slug;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(HellionStrings.Settings_Themes_ApplyChatColors_Keep))
|
||||
{
|
||||
_applyDismissedFor = active.Slug;
|
||||
}
|
||||
|
||||
ImGui.SetCursorScreenPos(origin + new Vector2(0f, height + 8f));
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
// ── Fonts ──────────────────────────────────────────────────────────────
|
||||
// R3 deliberately NOT applied here — the UseHellionFont/FontsEnabled
|
||||
// visibility chain has priority over type grouping (R4).
|
||||
|
||||
private void DrawFontsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Fonts);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
if (
|
||||
ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont)
|
||||
)
|
||||
{
|
||||
if (Mutable.UseHellionFont)
|
||||
Mutable.FontsEnabled = false;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Theme_UseHellionFont_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.UseHellionFont)
|
||||
{
|
||||
// Bundled-font path: only the base font size matters; the
|
||||
// global / japanese / italic chooser pickers do not apply.
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
var unused = false;
|
||||
if (!Mutable.UseHellionFont && !Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else if (!Mutable.UseHellionFont)
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_Font_Name,
|
||||
Mutable.GlobalFontV2,
|
||||
false,
|
||||
ref unused
|
||||
);
|
||||
globalChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.GlobalFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##global"))
|
||||
{
|
||||
Mutable.GlobalFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Font_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGuiUtil.WarningText(Language.Options_Font_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
var japaneseChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_JapaneseFont_Name,
|
||||
Mutable.JapaneseFontV2,
|
||||
false,
|
||||
ref unused,
|
||||
id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false,
|
||||
"いろはにほへと ちりぬるを"
|
||||
);
|
||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.JapaneseFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##japanese"))
|
||||
{
|
||||
Mutable.JapaneseFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
|
||||
var italicChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_ItalicFont_Name,
|
||||
Mutable.ItalicFontV2,
|
||||
true,
|
||||
ref Mutable.ItalicEnabled
|
||||
);
|
||||
italicChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.ItalicFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##italic"))
|
||||
{
|
||||
Mutable.ItalicEnabled = false;
|
||||
Mutable.ItalicFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Italic_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
// v1.5.3: ExtraGlyphRanges is an atlas-wide property and stays
|
||||
// reachable regardless of UseHellionFont / FontsEnabled state so
|
||||
// users can verify or override the auto-activation on language change.
|
||||
ImGui.Spacing();
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
var range = (int)Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
{
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra);
|
||||
}
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range;
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(
|
||||
Language.Options_SymbolsFontSize_Name,
|
||||
ref Mutable.SymbolsFontSizeV2
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Colours ────────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawColoursSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Colours);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
DrawColourPresetButtons();
|
||||
ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(
|
||||
Language.Options_ColorSelectedInputChannelButton_Name,
|
||||
ref Mutable.ColorSelectedInputChannelButton
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.UndoAlt,
|
||||
$"{type}",
|
||||
Language.Options_ChatColours_Reset
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.ChatColours.Remove(type);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.LongArrowAltDown,
|
||||
$"{type}",
|
||||
Language.Options_ChatColours_Import
|
||||
)
|
||||
)
|
||||
{
|
||||
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
|
||||
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = Mutable.ChatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
|
||||
{
|
||||
Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColourPresetButtons()
|
||||
{
|
||||
var first = true;
|
||||
foreach (var (_, preset) in ChatColourPresets.All)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
first = false;
|
||||
|
||||
if (preset.IsBrandPreset)
|
||||
{
|
||||
var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200));
|
||||
var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106));
|
||||
ImGui.PushStyleColor(
|
||||
ImGuiCol.Border,
|
||||
new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f)
|
||||
);
|
||||
ImGui.PushStyleColor(
|
||||
ImGuiCol.Button,
|
||||
new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f)
|
||||
);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f);
|
||||
}
|
||||
|
||||
if (ImGui.Button(GetPresetLabel(preset)))
|
||||
{
|
||||
ApplyPreset(preset);
|
||||
}
|
||||
|
||||
if (preset.IsBrandPreset)
|
||||
{
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.PopStyleColor(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPresetLabel(ChatColourPreset preset)
|
||||
{
|
||||
var localized = HellionStrings.ResourceManager.GetString(
|
||||
preset.LocalizationKey,
|
||||
HellionStrings.Culture
|
||||
);
|
||||
return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized;
|
||||
}
|
||||
|
||||
private void ApplyPreset(ChatColourPreset preset)
|
||||
{
|
||||
foreach (var (channel, colour) in preset.Colours)
|
||||
{
|
||||
Mutable.ChatColours[channel] = colour;
|
||||
}
|
||||
Plugin.SaveConfig();
|
||||
GlobalParametersCache.Refresh();
|
||||
_logger.LogDebug($"Applied chat colour preset: {preset.DisplayName}");
|
||||
}
|
||||
|
||||
// ── Window style ───────────────────────────────────────────────────────
|
||||
|
||||
private void DrawWindowStyleSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_WindowStyle);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_ShowTitleBar_Name, ref Mutable.ShowTitleBar);
|
||||
|
||||
ImGui.Checkbox(
|
||||
Language.Options_ShowPopOutTitleBar_Name,
|
||||
ref Mutable.ShowPopOutTitleBar
|
||||
);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_SidebarTabView_Name, ref Mutable.SidebarTabView);
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
if (Mutable.SidebarTabView)
|
||||
{
|
||||
var sidebarWidth = Mutable.SidebarWidth;
|
||||
if (
|
||||
ImGui.SliderInt(
|
||||
HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Name,
|
||||
ref sidebarWidth,
|
||||
44,
|
||||
160,
|
||||
$"{sidebarWidth} px"
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.SidebarWidth = sidebarWidth;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(
|
||||
HellionStrings.Settings_ThemeAndLayout_SidebarWidth_Description
|
||||
);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
// Slider range 50-100% maps to 0.5-1.0 internally. Floor at 50% prevents
|
||||
// accidentally hiding the chat background (v1.2.0 bug at WindowAlpha=0).
|
||||
var opacityPercent = Mutable.WindowOpacity * 100f;
|
||||
if (
|
||||
ImGuiUtil.DragFloatVertical(
|
||||
HellionStrings.Settings_ThemeAndLayout_WindowOpacity_Name,
|
||||
ref opacityPercent,
|
||||
.25f,
|
||||
50f,
|
||||
100f,
|
||||
$"{opacityPercent:N0}%%",
|
||||
ImGuiSliderFlags.AlwaysClamp
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.WindowOpacity = opacityPercent / 100f;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_ThemeAndLayout_WindowOpacity_Description);
|
||||
|
||||
// UI-12: inactive-window opacity, same 50-100% range and clamp.
|
||||
var inactiveOpacityPercent = Mutable.WindowOpacityInactive * 100f;
|
||||
if (
|
||||
ImGuiUtil.DragFloatVertical(
|
||||
HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Name,
|
||||
ref inactiveOpacityPercent,
|
||||
.25f,
|
||||
50f,
|
||||
100f,
|
||||
$"{inactiveOpacityPercent:N0}%%",
|
||||
ImGuiSliderFlags.AlwaysClamp
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.WindowOpacityInactive = inactiveOpacityPercent / 100f;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(
|
||||
HellionStrings.Settings_ThemeAndLayout_WindowOpacityInactive_Description
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Timestamps ─────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawTimestampSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Timestamps);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_PrettierTimestamps_Name,
|
||||
ref Mutable.PrettierTimestamps
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PrettierTimestamps_Description);
|
||||
|
||||
if (Mutable.PrettierTimestamps)
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_MoreCompactPretty_Name,
|
||||
ref Mutable.MoreCompactPretty
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_MoreCompactPretty_Description);
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Appearance_UseCompactDensity_Name,
|
||||
ref Mutable.UseCompactDensity
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Appearance_UseCompactDensity_Description);
|
||||
|
||||
ImGui.Checkbox(
|
||||
Language.Options_HideSameTimestamps_Name,
|
||||
ref Mutable.HideSameTimestamps
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideSameTimestamps_Description);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_Use24HourClock_Name, ref Mutable.Use24HourClock);
|
||||
ImGuiUtil.HelpMarker(Language.Options_Use24HourClock_Description);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Animations ─────────────────────────────────────────────────────────
|
||||
|
||||
private void DrawAnimationsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Animations);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Master accessibility toggle for the v1.5.4 motion work: the
|
||||
// theme crossfade, the sidebar/card hover lerps and the
|
||||
// unread-tab pulse all read Config.ReduceMotion and snap
|
||||
// instantly when it is on.
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_ThemeAndLayout_ReduceMotion_Name,
|
||||
ref Mutable.ReduceMotion
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_ThemeAndLayout_ReduceMotion_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
// Four sections: Auto-Tell Tabs, Behaviour, Preview, Emotes.
|
||||
// Six sections: Messages, Input & preview, Auto-tell tabs, Emotes, Links & tooltips, Novice network.
|
||||
internal sealed class Chat : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
@@ -40,37 +40,168 @@ internal sealed class Chat : ISettingsTab
|
||||
.ToArray(),
|
||||
};
|
||||
|
||||
public void Draw(bool changed)
|
||||
public void Draw(bool sectionJustEntered)
|
||||
{
|
||||
DrawAutoTellTabsSection();
|
||||
DrawMessagesSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawBehaviourSection();
|
||||
DrawInputPreviewSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawPreviewSection();
|
||||
DrawAutoTellTabsSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawEmotesSection();
|
||||
DrawEmotesSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawLinksTooltipsSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawNoviceNetworkSection(sectionJustEntered);
|
||||
}
|
||||
|
||||
private void DrawAutoTellTabsSection()
|
||||
private void DrawMessagesSection(bool sectionJustEntered)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_AutoTellTabs_Heading);
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Messages);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Checkboxes first.
|
||||
ImGui.Checkbox(
|
||||
Language.Options_CollapseDuplicateMessages_Name,
|
||||
ref Mutable.CollapseDuplicateMessages
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMessages_Description);
|
||||
|
||||
// Conditional child: only visible when parent is on (R4).
|
||||
if (Mutable.CollapseDuplicateMessages)
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_CollapseDuplicateMsgUniqueLink_Name,
|
||||
ref Mutable.CollapseKeepUniqueLinks
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Chat_NotifyFailedTell_Name,
|
||||
ref Mutable.NotifyFailedTell
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NotifyFailedTell_Description);
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Chat_NotifyPluginDisclosure_Name,
|
||||
ref Mutable.NotifyPluginDisclosure
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NotifyPluginDisclosure_Description);
|
||||
|
||||
// Dropdowns after checkboxes (R3).
|
||||
// UI-7: name display options.
|
||||
using (
|
||||
var combo = ImGuiUtil.BeginComboVertical(
|
||||
HellionStrings.Settings_Chat_WorldSuffix_Name,
|
||||
Mutable.WorldSuffixMode.Name()
|
||||
)
|
||||
)
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<WorldSuffixMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.Name(), Mutable.WorldSuffixMode == mode))
|
||||
Mutable.WorldSuffixMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_WorldSuffix_Description);
|
||||
|
||||
using (
|
||||
var combo = ImGuiUtil.BeginComboVertical(
|
||||
HellionStrings.Settings_Chat_NameForm_Name,
|
||||
Mutable.NameFormMode.Name()
|
||||
)
|
||||
)
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<NameFormMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.Name(), Mutable.NameFormMode == mode))
|
||||
Mutable.NameFormMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_NameForm_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawInputPreviewSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_InputPreview);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Checkboxes first.
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Chat_SymbolPicker_Enable_Name,
|
||||
ref Mutable.SymbolPickerEnabled
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_SymbolPicker_Enable_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_PreviewOnlyIf_Name, ref Mutable.OnlyPreviewIf);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PreviewOnlyIf_Description);
|
||||
|
||||
// Dropdown after checkboxes (R3).
|
||||
using (
|
||||
var combo = ImGuiUtil.BeginComboVertical(
|
||||
Language.Options_Preview_Name,
|
||||
Mutable.PreviewPosition.Name()
|
||||
)
|
||||
)
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var position in Enum.GetValues<PreviewPosition>())
|
||||
{
|
||||
if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position))
|
||||
Mutable.PreviewPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_Preview_Description);
|
||||
|
||||
// Number input last (R3).
|
||||
if (
|
||||
ImGuiUtil.InputIntVertical(
|
||||
Language.Options_PreviewMinimum_Name,
|
||||
Language.Options_PreviewMinimum_Description,
|
||||
ref Mutable.PreviewMinimum
|
||||
)
|
||||
)
|
||||
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAutoTellTabsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_AutoTellTabs);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Checkboxes first (R3).
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.ChatLog_AutoTellTabs_Enable_Name,
|
||||
ref Mutable.EnableAutoTellTabs
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description);
|
||||
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
var limit = Mutable.AutoTellTabsLimit;
|
||||
if (ImGui.SliderInt(HellionStrings.ChatLog_AutoTellTabs_Limit_Name, ref limit, 1, 50))
|
||||
Mutable.AutoTellTabsLimit = limit;
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description);
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.ChatLog_AutoTellTabs_Compact_Name,
|
||||
ref Mutable.AutoTellTabsCompactDisplay
|
||||
@@ -89,6 +220,13 @@ internal sealed class Chat : ISettingsTab
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description);
|
||||
|
||||
// Sliders after checkboxes (R3).
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
var limit = Mutable.AutoTellTabsLimit;
|
||||
if (ImGui.SliderInt(HellionStrings.ChatLog_AutoTellTabs_Limit_Name, ref limit, 1, 50))
|
||||
Mutable.AutoTellTabsLimit = limit;
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpText(HellionStrings.ChatLog_AutoTellTabs_PreloadHint);
|
||||
|
||||
@@ -117,85 +255,17 @@ internal sealed class Chat : ISettingsTab
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBehaviourSection()
|
||||
private void DrawEmotesSection(bool sectionJustEntered)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Behaviour_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_CollapseDuplicateMessages_Name,
|
||||
ref Mutable.CollapseDuplicateMessages
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMessages_Description);
|
||||
|
||||
if (Mutable.CollapseDuplicateMessages)
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_CollapseDuplicateMsgUniqueLink_Name,
|
||||
ref Mutable.CollapseKeepUniqueLinks
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(
|
||||
HellionStrings.Settings_Chat_SymbolPicker_Enable_Name,
|
||||
ref Mutable.SymbolPickerEnabled
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Settings_Chat_SymbolPicker_Enable_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPreviewSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Preview_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
using (
|
||||
var combo = ImGuiUtil.BeginComboVertical(
|
||||
Language.Options_Preview_Name,
|
||||
Mutable.PreviewPosition.Name()
|
||||
)
|
||||
)
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var position in Enum.GetValues<PreviewPosition>())
|
||||
{
|
||||
if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position))
|
||||
Mutable.PreviewPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_Preview_Description);
|
||||
|
||||
if (
|
||||
ImGuiUtil.InputIntVertical(
|
||||
Language.Options_PreviewMinimum_Name,
|
||||
Language.Options_PreviewMinimum_Description,
|
||||
ref Mutable.PreviewMinimum
|
||||
)
|
||||
)
|
||||
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
|
||||
|
||||
ImGui.Checkbox(Language.Options_PreviewOnlyIf_Name, ref Mutable.OnlyPreviewIf);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PreviewOnlyIf_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEmotesSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Emotes_Heading);
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Emotes);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Checkbox first (R3).
|
||||
ImGui.Checkbox(Language.Options_ShowEmotes_Name, ref Mutable.ShowEmotes);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowEmotes_Desc);
|
||||
|
||||
@@ -213,6 +283,7 @@ internal sealed class Chat : ISettingsTab
|
||||
WordPopupOptionsBuiltFor = EmoteCache.LoadingState.Done;
|
||||
}
|
||||
|
||||
// Button to add blocked emotes (R3 — button before table).
|
||||
var buttonWidth = ImGui.GetContentRegionAvail().X / 3;
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
|
||||
@@ -273,6 +344,7 @@ internal sealed class Chat : ISettingsTab
|
||||
$"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}"
|
||||
);
|
||||
|
||||
// 5-column loaded-emotes display table.
|
||||
using (
|
||||
var emoteTable = ImRaii.Table(
|
||||
"##LoadedEmotes",
|
||||
@@ -298,4 +370,54 @@ internal sealed class Chat : ISettingsTab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLinksTooltipsSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_LinksTooltips);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(
|
||||
Language.Options_NativeItemTooltips_Name,
|
||||
ref Mutable.NativeItemTooltips
|
||||
);
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
// Conditional slider: only shown when native tooltips are enabled (R4).
|
||||
if (Mutable.NativeItemTooltips)
|
||||
{
|
||||
ImGuiUtil.DragFloatVertical(
|
||||
Language.Options_TooltipOffset_Name,
|
||||
Language.Options_TooltipOffset_Desc,
|
||||
ref Mutable.TooltipOffset,
|
||||
1,
|
||||
0f,
|
||||
400f,
|
||||
$"{Mutable.TooltipOffset:N0}px",
|
||||
ImGuiSliderFlags.AlwaysClamp
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawNoviceNetworkSection(bool sectionJustEntered)
|
||||
{
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_NoviceNetwork);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+120
-21
@@ -15,16 +15,16 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class DataManagement : ISettingsTab
|
||||
internal sealed class DataAndPrivacy : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<DataManagement> _logger;
|
||||
private readonly ILogger<DataAndPrivacy> _logger;
|
||||
|
||||
public string Name =>
|
||||
HellionStrings.Settings_Card_DataManagement_Title + "###tabs-datamanagement";
|
||||
|
||||
// Cleanup state (was in Privacy.cs)
|
||||
// Cleanup state
|
||||
private Dictionary<int, long>? CleanupCounts;
|
||||
private long CleanupKeepCount;
|
||||
private long CleanupDeleteCount;
|
||||
@@ -33,7 +33,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
private HashSet<ChatType>? CleanupPreviewSnapshot;
|
||||
private bool RetentionRunning => Plugin.RetentionSweepRunning;
|
||||
|
||||
// Export form state (was in Privacy.cs)
|
||||
// Export form state
|
||||
private int ExportRangeDays = 30;
|
||||
private string ExportSenderSubstring = string.Empty;
|
||||
private readonly HashSet<ChatType> ExportSelectedChannels = [];
|
||||
@@ -138,36 +138,133 @@ internal sealed class DataManagement : ISettingsTab
|
||||
),
|
||||
];
|
||||
|
||||
internal DataManagement(Plugin plugin, Configuration mutable, ILogger<DataManagement> logger)
|
||||
internal DataAndPrivacy(Plugin plugin, Configuration mutable, ILogger<DataAndPrivacy> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
public void Draw(bool sectionJustEntered)
|
||||
{
|
||||
// Shift-on-open keeps the Advanced tools available without a permanent
|
||||
// toggle in the UI, mirroring upstream Chat 2 behaviour.
|
||||
if (changed)
|
||||
if (sectionJustEntered)
|
||||
ShowAdvanced = ImGui.GetIO().KeyShift;
|
||||
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawPrivacyFilterSection();
|
||||
ImGui.Spacing();
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawStorageSection();
|
||||
ImGui.Spacing();
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawRetentionSection();
|
||||
ImGui.Spacing();
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawCleanupSection();
|
||||
ImGui.Spacing();
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawExportSection();
|
||||
ImGui.Spacing();
|
||||
DrawDatabaseViewerSection();
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
DrawDatabaseSection();
|
||||
}
|
||||
|
||||
private void DrawPrivacyFilterSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_PrivacyFilter);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Wizard re-open sits outside the disabled block so it is always clickable.
|
||||
if (ImGui.Button(HellionStrings.Wizard_Reopen_Button))
|
||||
Plugin.FirstRunWizard.IsOpen = true;
|
||||
ImGui.Spacing();
|
||||
DrawAdvancedSection();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyFilterEnabled,
|
||||
HellionStrings.Privacy_FilterEnabled_Name,
|
||||
HellionStrings.Privacy_FilterEnabled_Description
|
||||
);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
// Whitelist, presets, and PersistUnknown are greyed (still visible)
|
||||
// when the filter is off — ImRaii.Disabled block preserved verbatim.
|
||||
using (ImRaii.Disabled(!Mutable.PrivacyFilterEnabled))
|
||||
{
|
||||
ImGuiUtil.HelpText(HellionStrings.Privacy_Whitelist_Help);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.Button(HellionStrings.Privacy_Preset_PrivacyFirst))
|
||||
Mutable.PrivacyPersistChannels = [.. PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(HellionStrings.Privacy_Preset_ClearAll))
|
||||
Mutable.PrivacyPersistChannels.Clear();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button(HellionStrings.Privacy_Preset_SelectAll))
|
||||
foreach (var group in Groups)
|
||||
foreach (var t in group.Types)
|
||||
Mutable.PrivacyPersistChannels.Add(t);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
foreach (var (heading, types) in Groups)
|
||||
{
|
||||
using var groupTree = ImRaii.TreeNode(heading());
|
||||
if (!groupTree.Success)
|
||||
continue;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
var enabled = Mutable.PrivacyPersistChannels.Contains(type);
|
||||
var label = type.ToString();
|
||||
if (ImGui.Checkbox($"{label}##privacy-{(int)type}", ref enabled))
|
||||
{
|
||||
if (enabled)
|
||||
Mutable.PrivacyPersistChannels.Add(type);
|
||||
else
|
||||
Mutable.PrivacyPersistChannels.Remove(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyPersistUnknownChannels,
|
||||
HellionStrings.Privacy_PersistUnknown_Name,
|
||||
HellionStrings.Privacy_PersistUnknown_Description
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStorageSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Storage_Heading);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Storage);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
@@ -245,7 +342,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
private void DrawRetentionSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Retention_Heading);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Retention);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
@@ -437,7 +534,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
private void DrawCleanupSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Cleanup_Heading);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Cleanup);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
@@ -627,7 +724,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
|
||||
private void DrawExportSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Export_Heading);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Export);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
@@ -785,9 +882,9 @@ internal sealed class DataManagement : ISettingsTab
|
||||
}.Start();
|
||||
}
|
||||
|
||||
private void DrawDatabaseViewerSection()
|
||||
private void DrawDatabaseSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_DbViewer_Heading);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Database);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
@@ -862,16 +959,17 @@ internal sealed class DataManagement : ISettingsTab
|
||||
NotificationType.Info
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAdvancedSection()
|
||||
{
|
||||
// Advanced sub-block: only visible when the tab was opened with Shift held.
|
||||
// Gate matches the Shift-on-open flag set at the top of Draw().
|
||||
if (!ShowAdvanced)
|
||||
return;
|
||||
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_DataManagement_Advanced_Heading);
|
||||
if (!tree.Success)
|
||||
ImGui.Spacing();
|
||||
using var advTree = ImRaii.TreeNode(
|
||||
HellionStrings.Settings_DataManagement_Advanced_Heading
|
||||
);
|
||||
if (!advTree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
@@ -907,6 +1005,7 @@ internal sealed class DataManagement : ISettingsTab
|
||||
new Thread(() => InsertMessages(10_000)).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertMessages(int count)
|
||||
{
|
||||
@@ -1,320 +0,0 @@
|
||||
using Dalamud;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class FontsAndColours : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
private readonly ILogger<FontsAndColours> _logger;
|
||||
|
||||
public string Name =>
|
||||
HellionStrings.Settings_Card_FontsAndColours_Title + "###tabs-fontsandcolours";
|
||||
|
||||
internal FontsAndColours(Plugin plugin, Configuration mutable, ILogger<FontsAndColours> logger)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
DrawFontsSection();
|
||||
ImGui.Spacing();
|
||||
DrawColoursSection();
|
||||
}
|
||||
|
||||
private void DrawFontsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_FontsAndColours_Fonts_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
if (
|
||||
ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont)
|
||||
)
|
||||
{
|
||||
if (Mutable.UseHellionFont)
|
||||
Mutable.FontsEnabled = false;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Theme_UseHellionFont_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.UseHellionFont)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.FontSizeCombo(
|
||||
Language.Options_SymbolsFontSize_Name,
|
||||
ref Mutable.SymbolsFontSizeV2
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
|
||||
var unused = false;
|
||||
if (!Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_Font_Name,
|
||||
Mutable.GlobalFontV2,
|
||||
false,
|
||||
ref unused
|
||||
);
|
||||
globalChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.GlobalFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##global"))
|
||||
{
|
||||
Mutable.GlobalFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Font_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGuiUtil.WarningText(Language.Options_Font_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
var japaneseChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_JapaneseFont_Name,
|
||||
Mutable.JapaneseFontV2,
|
||||
false,
|
||||
ref unused,
|
||||
id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false,
|
||||
"いろはにほへと ちりぬるを"
|
||||
);
|
||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.JapaneseFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##japanese"))
|
||||
{
|
||||
Mutable.JapaneseFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
|
||||
var italicChooser = ImGuiUtil.FontChooser(
|
||||
Language.Options_ItalicFont_Name,
|
||||
Mutable.ItalicFontV2,
|
||||
true,
|
||||
ref Mutable.ItalicEnabled
|
||||
);
|
||||
italicChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.ItalicFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##italic"))
|
||||
{
|
||||
Mutable.ItalicEnabled = false;
|
||||
Mutable.ItalicFontV2 = new SingleFontSpec
|
||||
{
|
||||
FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular),
|
||||
SizePt = 12.75f,
|
||||
};
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Italic_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName)
|
||||
);
|
||||
|
||||
var range = (int)Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
{
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra);
|
||||
}
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(
|
||||
Language.Options_SymbolsFontSize_Name,
|
||||
ref Mutable.SymbolsFontSizeV2
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColoursSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_FontsAndColours_Colours_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
DrawColourPresetButtons();
|
||||
ImGui.TextDisabled(HellionStrings.Settings_Appearance_Colours_PresetsHint);
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(
|
||||
Language.Options_ColorSelectedInputChannelButton_Name,
|
||||
ref Mutable.ColorSelectedInputChannelButton
|
||||
);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ColorSelectedInputChannelButton_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.UndoAlt,
|
||||
$"{type}",
|
||||
Language.Options_ChatColours_Reset
|
||||
)
|
||||
)
|
||||
{
|
||||
Mutable.ChatColours.Remove(type);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (
|
||||
ImGuiUtil.IconButton(
|
||||
FontAwesomeIcon.LongArrowAltDown,
|
||||
$"{type}",
|
||||
Language.Options_ChatColours_Import
|
||||
)
|
||||
)
|
||||
{
|
||||
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
|
||||
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = Mutable.ChatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
|
||||
{
|
||||
Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColourPresetButtons()
|
||||
{
|
||||
var first = true;
|
||||
foreach (var (_, preset) in ChatColourPresets.All)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
}
|
||||
first = false;
|
||||
|
||||
if (preset.IsBrandPreset)
|
||||
{
|
||||
var border = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(255, 128, 200));
|
||||
var btn = ColourUtil.RgbaToVector3(ColourUtil.ComponentsToRgba(74, 42, 106));
|
||||
ImGui.PushStyleColor(
|
||||
ImGuiCol.Border,
|
||||
new System.Numerics.Vector4(border.X, border.Y, border.Z, 1f)
|
||||
);
|
||||
ImGui.PushStyleColor(
|
||||
ImGuiCol.Button,
|
||||
new System.Numerics.Vector4(btn.X, btn.Y, btn.Z, 1f)
|
||||
);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1.5f);
|
||||
}
|
||||
|
||||
if (ImGui.Button(GetPresetLabel(preset)))
|
||||
{
|
||||
ApplyPreset(preset);
|
||||
}
|
||||
|
||||
if (preset.IsBrandPreset)
|
||||
{
|
||||
ImGui.PopStyleVar();
|
||||
ImGui.PopStyleColor(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPresetLabel(ChatColourPreset preset)
|
||||
{
|
||||
var localized = HellionStrings.ResourceManager.GetString(
|
||||
preset.LocalizationKey,
|
||||
HellionStrings.Culture
|
||||
);
|
||||
return string.IsNullOrEmpty(localized) ? preset.DisplayName : localized;
|
||||
}
|
||||
|
||||
private void ApplyPreset(ChatColourPreset preset)
|
||||
{
|
||||
foreach (var (channel, colour) in preset.Colours)
|
||||
{
|
||||
Mutable.ChatColours[channel] = colour;
|
||||
}
|
||||
Plugin.SaveConfig();
|
||||
GlobalParametersCache.Refresh();
|
||||
_logger.LogDebug($"Applied chat colour preset: {preset.DisplayName}");
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,25 @@ internal sealed class General : ISettingsTab
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
public void Draw(bool sectionJustEntered)
|
||||
{
|
||||
DrawInputSection();
|
||||
DrawInputSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawAudioSection();
|
||||
DrawSoundSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawPerformanceSection();
|
||||
DrawLanguageSection(sectionJustEntered);
|
||||
ImGui.Spacing();
|
||||
DrawLanguageSection();
|
||||
DrawPerformanceSection(sectionJustEntered);
|
||||
}
|
||||
|
||||
private void DrawInputSection()
|
||||
private void DrawInputSection(bool sectionJustEntered)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Input_Heading);
|
||||
// Collapse every time the tab is freshly entered so state doesn't bleed across sessions.
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Input);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
@@ -83,53 +84,61 @@ internal sealed class General : ISettingsTab
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAudioSection()
|
||||
private void DrawSoundSection(bool sectionJustEntered)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Audio_Heading);
|
||||
// Collapse every time the tab is freshly entered so state doesn't bleed across sessions.
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Sound);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PlaySounds_Name, ref Mutable.PlaySounds);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPerformanceSection()
|
||||
// Volume is stored as a 0-1 float but shown as 0-100% to match user
|
||||
// intuition. Full range — unlike opacity there is no unsafe floor.
|
||||
var customSoundVolumePercent = Mutable.CustomSoundVolume * 100f;
|
||||
if (
|
||||
ImGuiUtil.DragFloatVertical(
|
||||
HellionStrings.Settings_General_CustomSoundVolume_Name,
|
||||
ref customSoundVolumePercent,
|
||||
1f,
|
||||
0f,
|
||||
100f,
|
||||
$"{customSoundVolumePercent:N0}%%",
|
||||
ImGuiSliderFlags.AlwaysClamp
|
||||
)
|
||||
)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Performance_Heading);
|
||||
Mutable.CustomSoundVolume = customSoundVolumePercent / 100f;
|
||||
}
|
||||
// Show the functional description and the per-tab navigation hint together.
|
||||
ImGuiUtil.HelpMarker(
|
||||
HellionStrings.Settings_General_CustomSoundVolume_Description
|
||||
+ "\n\n"
|
||||
+ HellionStrings.Settings_Section_Sound_TabsHint
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLanguageSection(bool sectionJustEntered)
|
||||
{
|
||||
// Collapse every time the tab is freshly entered so state doesn't bleed across sessions.
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Language);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt(Language.Options_MaxLinesToShow_Name, ref Mutable.MaxLinesToRender))
|
||||
{
|
||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_MaxLinesToShow_Description);
|
||||
}
|
||||
}
|
||||
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SortAutoTranslate_Description);
|
||||
|
||||
private void DrawLanguageSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Language_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ImGui.Spacing();
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
using (
|
||||
var combo = ImGuiUtil.BeginComboVertical(
|
||||
Language.Options_Language_Name,
|
||||
@@ -139,7 +148,12 @@ internal sealed class General : ISettingsTab
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LanguageOverride>())
|
||||
// None pinned first, then alphabetical by endonym so source order
|
||||
// (append-only for serialisation safety) is not visible to users.
|
||||
var sortedLanguages = Enum.GetValues<LanguageOverride>()
|
||||
.OrderBy(l => l == LanguageOverride.None ? 0 : 1)
|
||||
.ThenBy(l => l.Name(), StringComparer.InvariantCulture);
|
||||
foreach (var language in sortedLanguages)
|
||||
{
|
||||
if (ImGui.Selectable(language.Name()))
|
||||
{
|
||||
@@ -151,6 +165,9 @@ internal sealed class General : ISettingsTab
|
||||
ImGuiUtil.HelpMarker(
|
||||
string.Format(Language.Options_Language_Description, Plugin.PluginName)
|
||||
);
|
||||
// v1.5.3: HellionChat's font stack covers 24 languages but FFXIV's
|
||||
// engine only supports EN/DE/FR/JA for chat input/sending.
|
||||
ImGuiUtil.WarningText(HellionStrings.Settings_Language_FFXIVCoverage_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
using (
|
||||
@@ -175,9 +192,26 @@ internal sealed class General : ISettingsTab
|
||||
string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName)
|
||||
);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SortAutoTranslate_Description);
|
||||
private void DrawPerformanceSection(bool sectionJustEntered)
|
||||
{
|
||||
// Collapse every time the tab is freshly entered so state doesn't bleed across sessions.
|
||||
if (sectionJustEntered)
|
||||
ImGui.SetNextItemOpen(false);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Section_Performance);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt(Language.Options_MaxLinesToShow_Name, ref Mutable.MaxLinesToRender))
|
||||
{
|
||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_MaxLinesToShow_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ namespace HellionChat.Ui.SettingsTabs;
|
||||
internal interface ISettingsTab
|
||||
{
|
||||
string Name { get; }
|
||||
void Draw(bool changed);
|
||||
void Draw(bool sectionJustEntered);
|
||||
}
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using HellionChat.Branding;
|
||||
using HellionChat.Resources;
|
||||
using HellionChat.Util;
|
||||
|
||||
namespace HellionChat.Ui.SettingsTabs;
|
||||
|
||||
// Combines the former About and Changelog tabs into three collapsible sections.
|
||||
internal sealed class Information : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Information + "###tabs-information";
|
||||
|
||||
private readonly List<string> Translators =
|
||||
[
|
||||
"q673135110",
|
||||
"Akizem",
|
||||
"d0tiKs",
|
||||
"Moonlight_Everlit",
|
||||
"Dark32",
|
||||
"andreycout",
|
||||
"Button_",
|
||||
"Cali666",
|
||||
"cassandra308",
|
||||
"lokinmodar",
|
||||
"jtabox",
|
||||
"AkiraYorumoto",
|
||||
"MKhayle",
|
||||
"elena.space",
|
||||
"imlisa",
|
||||
"andrei5125",
|
||||
"ShivaMaheshvara",
|
||||
"aislinn87",
|
||||
"nishinatsu051",
|
||||
"lichuyuan",
|
||||
"Risu64",
|
||||
"yummypillow",
|
||||
"witchymary",
|
||||
"Yuzumi",
|
||||
"zomsakura",
|
||||
"Sirayuki",
|
||||
];
|
||||
|
||||
internal Information(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
Translators.Sort(
|
||||
(a, b) =>
|
||||
string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal)
|
||||
);
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
DrawHellionForgeSection();
|
||||
ImGui.Spacing();
|
||||
DrawVersionInfoSection();
|
||||
ImGui.Spacing();
|
||||
DrawAboutSection();
|
||||
ImGui.Spacing();
|
||||
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)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
using (Plugin.Interface.UiBuilder.MonoFontHandle.Push())
|
||||
ImGui.TextUnformatted(HellionForgeAscii.FoxBanner);
|
||||
}
|
||||
|
||||
private void DrawVersionInfoSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_VersionInfo_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Authors);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Discord);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama");
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Version);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(
|
||||
ImGuiColors.ParsedOrange,
|
||||
Plugin.Interface.Manifest.AssemblyVersion.ToString(3)
|
||||
);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||
Plugin.PlatformUtil.OpenLink(
|
||||
"https://gitea.hellion-forge.cloud/JonKazama-Hellion/HellionChat/issues"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAboutSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_About_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia"))
|
||||
Plugin.PlatformUtil.OpenLink("https://hellion-media.de");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream"))
|
||||
Plugin.PlatformUtil.OpenLink("https://github.com/Infiziert90/ChatTwo");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P2);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var translatorTree = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode))
|
||||
{
|
||||
if (translatorTree)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false);
|
||||
foreach (var translator in Translators)
|
||||
ImGui.TextUnformatted(translator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChangelogSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_Changelog_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PrintChangelog_Name, ref Mutable.PrintChangelog);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PrintChangelog_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var changelog = Plugin.Interface.Manifest.Changelog;
|
||||
if (changelog == null)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Changelog_Header);
|
||||
ImGui.TextUnformatted(
|
||||
$"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}"
|
||||
);
|
||||
ImGui.Spacing();
|
||||
foreach (var sentence in changelog.Split("\n"))
|
||||
{
|
||||
if (sentence == string.Empty)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
var indented = sentence.StartsWith('-') || sentence.StartsWith(" -");
|
||||
using var indent = ImRaii.PushIndent(10.0f, true, indented);
|
||||
ImGui.TextUnformatted(sentence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user