Every field here has a reader in the running plugin and had no control in the window. The config file was the only way to any of them. HideChat is the one that mattered. It defaults to on, it suppresses the game's own chat window, and the only other path to it is a right-click item that sets it to true and can never set it back. Anyone who used that item once had to edit JSON to get their chat back. The new toggle reads and writes the same field, so the two agree instead of fighting. The rest, by tab: - Window: the three remaining hide conditions, the preview minimum that belongs to a toggle already on screen, native item tooltips and their offset. - Chat: an emotes section holding the BetterTTV switch, the cache state, and an editor for the blocked-code list that has been in the config since v1.0 with nowhere to edit it. Blocking one code is a finer instrument than switching emotes off wholesale. Plus auto-translate sorting. - General: the failed-tell warning. The game reports a failed tell in the log only, where somebody who is typing does not see it. - Data & Privacy: battle messages. It decides whether battle lines are persisted at all, which makes it a storage decision rather than a chat one, and it sits in front of the whitelist rather than inside it. MaxParallelPopouts stays out. It is read once in the pool's constructor, the windows are registered once, and registering at runtime is explicitly forbidden -- a slider would do nothing until a reload, which is exactly the kind of decoration this cycle exists to remove. Four new strings in all 25 languages; the other fifteen labels were already translated and waiting.
213 lines
8.2 KiB
C#
213 lines
8.2 KiB
C#
using Dalamud.Bindings.ImGui;
|
|
using HellionChat.Resources;
|
|
using HellionChat.Ui.StyleEngine;
|
|
using HellionChat.Util;
|
|
|
|
namespace HellionChat.Ui.Components.Settings.Tabs;
|
|
|
|
internal sealed class GeneralTab
|
|
{
|
|
private readonly Plugin _plugin;
|
|
private readonly SettingsWidgets _w;
|
|
private readonly FontManager _fonts;
|
|
|
|
// Sorted once: the 25 endonyms are fixed literals, so the order never
|
|
// changes, and rebuilding it per frame costs an Enum.GetValues plus the
|
|
// whole LINQ chain. None is pinned to the front rather than sorted, so its
|
|
// localised label never affects the ordering.
|
|
private static readonly LanguageOverride[] LanguageOrder = BuildLanguageOrder();
|
|
|
|
public GeneralTab(Plugin plugin, FontManager fonts, TokenResolver resolver)
|
|
{
|
|
_plugin = plugin;
|
|
_w = new SettingsWidgets(plugin, new SettingsPalette(resolver));
|
|
_fonts = fonts;
|
|
}
|
|
|
|
private static LanguageOverride[] BuildLanguageOrder()
|
|
{
|
|
var all = Enum.GetValues<LanguageOverride>()
|
|
.Where(l => l != LanguageOverride.None)
|
|
.OrderBy(l => l.Name(), StringComparer.CurrentCulture);
|
|
return new[] { LanguageOverride.None }.Concat(all).ToArray();
|
|
}
|
|
|
|
public void Draw()
|
|
{
|
|
if (_w.Section(ImGui.GetID("general.behaviour"u8), "Behaviour"))
|
|
{
|
|
_w.ToggleRow(
|
|
ImGui.GetID("general.behaviour.reducemotion"u8),
|
|
HellionStrings.Settings_ThemeAndLayout_ReduceMotion_Name,
|
|
"Turns off theme crossfades, hover fades and the drifting motes.",
|
|
() => Plugin.Config.ReduceMotion,
|
|
v => Plugin.Config.ReduceMotion = v
|
|
);
|
|
DrawLanguagePicker();
|
|
}
|
|
|
|
if (_w.Section(ImGui.GetID("general.keybinds"u8), "Keybinds"))
|
|
{
|
|
ImGui.TextDisabled("Click a button, then press the key combination. Esc clears.");
|
|
DrawKeybind(
|
|
"Cycle to next chat tab",
|
|
"ChatTabForwardKeybind",
|
|
() => Plugin.Config.ChatTabForward,
|
|
v => Plugin.Config.ChatTabForward = v
|
|
);
|
|
DrawKeybind(
|
|
"Cycle to previous chat tab",
|
|
"ChatTabBackwardKeybind",
|
|
() => Plugin.Config.ChatTabBackward,
|
|
v => Plugin.Config.ChatTabBackward = v
|
|
);
|
|
DrawKeybindModePicker();
|
|
}
|
|
|
|
if (_w.Section(ImGui.GetID("general.notifications"u8), "Notifications"))
|
|
{
|
|
// The game reports a failed tell in the log only, where a player who
|
|
// is typing does not see it. On by default and never reachable.
|
|
_w.ToggleRow(
|
|
ImGui.GetID("general.notifications.failedtell"u8),
|
|
HellionStrings.Settings_NotifyFailedTell_Name,
|
|
HellionStrings.Settings_NotifyFailedTell_Description,
|
|
() => Plugin.Config.NotifyFailedTell,
|
|
v => Plugin.Config.NotifyFailedTell = v
|
|
);
|
|
|
|
_w.ToggleRow(
|
|
ImGui.GetID("general.notifications.sounds"u8),
|
|
Language.Options_PlaySounds_Name,
|
|
Language.Options_PlaySounds_Description,
|
|
() => Plugin.Config.PlaySounds,
|
|
v => Plugin.Config.PlaySounds = v
|
|
);
|
|
_w.SliderFloatRow(
|
|
ImGui.GetID("general.notifications.volume"u8),
|
|
HellionStrings.Settings_General_CustomSoundVolume_Name,
|
|
null,
|
|
() => Plugin.Config.CustomSoundVolume,
|
|
v => Plugin.Config.CustomSoundVolume = v,
|
|
0f,
|
|
1f
|
|
);
|
|
}
|
|
}
|
|
|
|
// Four steps, all of them required. The glyph-range activation used to live
|
|
// in Settings.Apply, which has not existed since the v1.6.0 rewrite -- see
|
|
// the comment at Plugin.cs:290. Without steps 2 and 4 a switch to Korean
|
|
// renders empty boxes until the plugin reloads.
|
|
private void DrawLanguagePicker()
|
|
{
|
|
var current = Plugin.Config.LanguageOverride;
|
|
var selected = Array.IndexOf(LanguageOrder, current);
|
|
var labels = new string[LanguageOrder.Length];
|
|
for (var i = 0; i < LanguageOrder.Length; i++)
|
|
labels[i] = LanguageOrder[i].Name();
|
|
|
|
_w.Row(
|
|
ImGui.GetID("general.behaviour.language"u8),
|
|
Language.Options_Language_Name,
|
|
"Switching rebuilds the font atlas, so the chat goes blank for a moment.",
|
|
ctx =>
|
|
{
|
|
ImGui.SetNextItemWidth(ctx.ControlWidth);
|
|
if (ImGui.Combo("##hc-language", ref selected, labels, labels.Length))
|
|
{
|
|
if (selected >= 0 && selected < LanguageOrder.Length)
|
|
ApplyLanguage(LanguageOrder[selected]);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
private void ApplyLanguage(LanguageOverride picked)
|
|
{
|
|
// Combo only reports real changes, but a rebuild is expensive enough
|
|
// that a future caller should not be able to trigger a no-op one.
|
|
if (picked == Plugin.Config.LanguageOverride)
|
|
return;
|
|
|
|
Plugin.Config.LanguageOverride = picked;
|
|
|
|
var required = picked.RequiredGlyphRanges();
|
|
if (required != 0 && !Plugin.Config.ExtraGlyphRanges.HasFlag(required))
|
|
Plugin.Config.ExtraGlyphRanges |= required;
|
|
|
|
_plugin.SaveConfig();
|
|
|
|
// Instance method, and the argument only matters when the override is
|
|
// None: LanguageChanged reads the config itself and falls back to the
|
|
// parameter only in that case. Passing picked.Code() there yields "",
|
|
// so "follow Dalamud" would silently mean English.
|
|
_plugin.LanguageChanged(Plugin.Interface.UiLanguage);
|
|
|
|
// Reads Config.ExtraGlyphRanges via SetUpRanges, so it has to come after
|
|
// the OR above.
|
|
//
|
|
// This runs inside Plugin.Draw's open font push (Plugin.cs:1105) and
|
|
// disposes the very handle that is on the ImGui font stack. It is safe
|
|
// because Dalamud keeps the ImFont alive under a per-frame lock until
|
|
// the frame ends -- not merely because we are on the draw thread, which
|
|
// is what the old comment claimed.
|
|
//
|
|
// The rebuild is asynchronous: FontsReady goes false until the atlas is
|
|
// done, and every chat component returns early meanwhile. For CJK that
|
|
// is visible as a blank chat window for a moment.
|
|
_fonts.RebuildDelegateFonts();
|
|
}
|
|
|
|
private void DrawKeybindModePicker()
|
|
{
|
|
var values = Enum.GetValues<KeybindMode>();
|
|
var current = Plugin.Config.KeybindMode;
|
|
var selected = Array.IndexOf(values, current);
|
|
var labels = new string[values.Length];
|
|
for (var i = 0; i < values.Length; i++)
|
|
labels[i] = values[i].Name();
|
|
|
|
_w.Row(
|
|
ImGui.GetID("general.keybinds.mode"u8),
|
|
Language.Options_KeybindMode_Name,
|
|
Plugin.Config.KeybindMode.Tooltip(),
|
|
ctx =>
|
|
{
|
|
ImGui.SetNextItemWidth(ctx.ControlWidth);
|
|
if (ImGui.Combo("##hc-keybindmode", ref selected, labels, labels.Length))
|
|
{
|
|
if (selected >= 0 && selected < values.Length)
|
|
{
|
|
Plugin.Config.KeybindMode = values[selected];
|
|
_plugin.SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
// Wires the already-present ImGuiUtil.KeybindInput capture widget (dead/unwired
|
|
// since the v1.6.0 rewrite) back into the settings, so ChatTabForward/Backward
|
|
// are bindable again. ConfigKeyBind is a reference type, so a capture (new
|
|
// instance) or an Esc-clear (null) changes the reference — persist only then.
|
|
private void DrawKeybind(
|
|
string label,
|
|
string id,
|
|
Func<ConfigKeyBind?> get,
|
|
Action<ConfigKeyBind?> set
|
|
)
|
|
{
|
|
ImGui.TextUnformatted(label);
|
|
ImGui.SetNextItemWidth(-1);
|
|
var keybind = get();
|
|
var before = keybind;
|
|
ImGuiUtil.KeybindInput(id, ref keybind);
|
|
if (!ReferenceEquals(before, keybind))
|
|
{
|
|
set(keybind);
|
|
_plugin.SaveConfig();
|
|
}
|
|
}
|
|
}
|