c4c85cf4b8
- Translated project documentation (LEARNING-JOURNEY, CONTRIBUTORS, AI_DISCLOSURE) to English for better accessibility. - Standardized internal code documentation by converting XML-doc blocks to standard comment format. - Cleaned up inline comments and removed redundant versioning metadata across the codebase. - Refactored non-functional text elements to improve readability and maintain a consistent style.
216 lines
7.5 KiB
C#
216 lines
7.5 KiB
C#
using System;
|
|
using System.Numerics;
|
|
using Dalamud.Bindings.ImGui;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using HellionChat._Helpers;
|
|
using HellionChat.Code;
|
|
using HellionChat.Util;
|
|
|
|
namespace HellionChat.Ui;
|
|
|
|
// Input bar component for pop-out windows. Render() is a stub — the main
|
|
// window input layer stays in ChatLogWindow to avoid a high-risk extract.
|
|
// RenderCompact() is the only v0.6.0 deliverable; Render() can be filled
|
|
// in a later cycle if needed.
|
|
public sealed class ChatInputBar
|
|
{
|
|
private readonly Plugin _plugin;
|
|
private readonly ChatLogWindow _host;
|
|
private readonly Func<Tab?> _activeTabAccessor;
|
|
private readonly InputState _state = new();
|
|
|
|
public ChatInputBar(Plugin plugin, ChatLogWindow host, Func<Tab?> activeTabAccessor)
|
|
{
|
|
_plugin = plugin;
|
|
_host = host;
|
|
_activeTabAccessor = activeTabAccessor;
|
|
}
|
|
|
|
public InputState State => _state;
|
|
public bool IsFocused { get; private set; }
|
|
|
|
// Stub — main window input is handled in ChatLogWindow.
|
|
public void Render() { }
|
|
|
|
// Compact layout for pop-out windows: channel icon button left, text
|
|
// input right. Auto-translate is intentionally excluded — the upstream
|
|
// popup isn't instanciable per window without a larger refactor, and
|
|
// typical pop-out use cases rarely need it. Can be added later if
|
|
// tester feedback warrants it.
|
|
//
|
|
// Channel switching is global via Plugin.Functions.Chat (FFXIV API).
|
|
// Text buffer and history cursor are independent per pop-out.
|
|
public void RenderCompact()
|
|
{
|
|
var tab = _activeTabAccessor();
|
|
if (tab == null)
|
|
return;
|
|
|
|
DrawChannelIconButton(tab);
|
|
ImGui.SameLine();
|
|
DrawCompactInput(tab);
|
|
}
|
|
|
|
private void DrawCompactInput(Tab tab)
|
|
{
|
|
var inputWidth = ImGui.GetContentRegionAvail().X;
|
|
if (inputWidth < 60f)
|
|
inputWidth = 60f;
|
|
|
|
ImGui.SetNextItemWidth(inputWidth);
|
|
|
|
// CallbackHistory wires Up/Down navigation to InputHistoryService.
|
|
// Submit detected via IsItemDeactivated + Enter, not EnterReturnsTrue
|
|
// (matches ChatLogWindow behavior).
|
|
const ImGuiInputTextFlags flags = ImGuiInputTextFlags.CallbackHistory;
|
|
ImGui.InputText(
|
|
$"##chat-compact-input-{tab.Identifier}",
|
|
ref _state.Buffer,
|
|
500,
|
|
flags,
|
|
CompactCallback
|
|
);
|
|
|
|
IsFocused = ImGui.IsItemActive();
|
|
|
|
if (
|
|
ImGui.IsItemDeactivated()
|
|
&& (ImGui.IsKeyDown(ImGuiKey.Enter) || ImGui.IsKeyDown(ImGuiKey.KeypadEnter))
|
|
)
|
|
{
|
|
SubmitCompact(tab);
|
|
}
|
|
}
|
|
|
|
// TEST-MIRROR: ../_Helpers/CompactInputSubmitter.cs
|
|
private void SubmitCompact(Tab tab) =>
|
|
CompactInputSubmitter.TrySubmit(_state, tab, _host.SendChatBoxFromExternal);
|
|
|
|
// History navigation callback. Cursor math delegated to
|
|
// CompactInputHistoryNavigator; ImGui buffer splice stays here.
|
|
// TEST-MIRROR: ../_Helpers/CompactInputHistoryNavigator.cs
|
|
private int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
|
{
|
|
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
|
return 0;
|
|
|
|
var direction = data.EventKey switch
|
|
{
|
|
ImGuiKey.UpArrow => CompactInputHistoryNavigator.Direction.Up,
|
|
ImGuiKey.DownArrow => CompactInputHistoryNavigator.Direction.Down,
|
|
_ => (CompactInputHistoryNavigator.Direction?)null,
|
|
};
|
|
if (direction is null)
|
|
return 0;
|
|
|
|
var (cursor, replacement) = CompactInputHistoryNavigator.Navigate(
|
|
direction.Value,
|
|
_state.HistoryCursor,
|
|
_state.Buffer,
|
|
() => InputHistoryService.Count,
|
|
InputHistoryService.Push,
|
|
InputHistoryService.GetByCursor
|
|
);
|
|
|
|
_state.HistoryCursor = cursor;
|
|
if (replacement is null)
|
|
return 0;
|
|
|
|
data.DeleteChars(0, data.BufTextLen);
|
|
data.InsertChars(0, replacement);
|
|
return 0;
|
|
}
|
|
|
|
private void DrawChannelIconButton(Tab tab)
|
|
{
|
|
var inputType = tab.CurrentChannel.UseTempChannel
|
|
? tab.CurrentChannel.TempChannel.ToChatType()
|
|
: tab.CurrentChannel.Channel.ToChatType();
|
|
|
|
var rgba = Plugin.Config.ChatColours.TryGetValue(inputType, out var c)
|
|
? c
|
|
: (inputType.DefaultColor() ?? 0xFFFFFFFFu);
|
|
var v3 = ColourUtil.RgbaToVector3(rgba);
|
|
var bg = new Vector4(v3.X, v3.Y, v3.Z, 1f);
|
|
|
|
// Black foreground on bright backgrounds, white on dark.
|
|
var luminance = 0.2126f * v3.X + 0.7152f * v3.Y + 0.0722f * v3.Z;
|
|
var fg = luminance > 0.55f ? new Vector4(0f, 0f, 0f, 1f) : new Vector4(1f, 1f, 1f, 1f);
|
|
|
|
const string popupId = "chat-channel-picker-compact";
|
|
const float buttonSize = 22f;
|
|
|
|
using (ImRaii.PushColor(ImGuiCol.Button, bg))
|
|
using (ImRaii.PushColor(ImGuiCol.ButtonHovered, bg))
|
|
using (ImRaii.PushColor(ImGuiCol.ButtonActive, bg))
|
|
using (ImRaii.PushColor(ImGuiCol.Text, fg))
|
|
{
|
|
// Single-letter glyph as a quick visual cue until a proper icon font lands.
|
|
var label = ChannelGlyph(inputType);
|
|
if (
|
|
ImGui.Button($"{label}##chan-compact", new Vector2(buttonSize, buttonSize))
|
|
&& tab.Channel is null
|
|
)
|
|
ImGui.OpenPopup(popupId);
|
|
}
|
|
|
|
if (tab.Channel is not null && ImGui.IsItemHovered())
|
|
ImGui.SetTooltip(Resources.Language.ChatLog_SwitcherDisabled);
|
|
else if (ImGui.IsItemHovered())
|
|
ImGui.SetTooltip(inputType.Name());
|
|
|
|
using (var popup = ImRaii.Popup(popupId))
|
|
{
|
|
if (popup)
|
|
{
|
|
var channels = _host.GetValidChannels();
|
|
foreach (var (name, channel) in channels)
|
|
if (ImGui.Selectable(name))
|
|
_host.SetChannel(channel);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string ChannelGlyph(ChatType type) =>
|
|
type switch
|
|
{
|
|
ChatType.Say => "S",
|
|
ChatType.Yell => "Y",
|
|
ChatType.Shout => "!",
|
|
ChatType.TellIncoming or ChatType.TellOutgoing => "T",
|
|
ChatType.Party or ChatType.CrossParty => "P",
|
|
ChatType.Alliance => "A",
|
|
ChatType.FreeCompany => "F",
|
|
ChatType.NoviceNetwork => "N",
|
|
ChatType.Linkshell1 => "1",
|
|
ChatType.Linkshell2 => "2",
|
|
ChatType.Linkshell3 => "3",
|
|
ChatType.Linkshell4 => "4",
|
|
ChatType.Linkshell5 => "5",
|
|
ChatType.Linkshell6 => "6",
|
|
ChatType.Linkshell7 => "7",
|
|
ChatType.Linkshell8 => "8",
|
|
ChatType.CrossLinkshell1 => "①",
|
|
ChatType.CrossLinkshell2 => "②",
|
|
ChatType.CrossLinkshell3 => "③",
|
|
ChatType.CrossLinkshell4 => "④",
|
|
ChatType.CrossLinkshell5 => "⑤",
|
|
ChatType.CrossLinkshell6 => "⑥",
|
|
ChatType.CrossLinkshell7 => "⑦",
|
|
ChatType.CrossLinkshell8 => "⑧",
|
|
_ => "?",
|
|
};
|
|
|
|
// Forwards a tab-cycle keybind delta to the host (single source of truth).
|
|
public void HandleKeybindForward(int delta) => _host.ChangeTabDelta(delta);
|
|
}
|
|
|
|
// Per-window input state. Each ChatInputBar owns one so pop-outs and the
|
|
// main window keep independent buffers and history cursors.
|
|
public sealed class InputState
|
|
{
|
|
public string Buffer = string.Empty;
|
|
public InputChannel? Channel;
|
|
public int HistoryCursor = -1;
|
|
}
|