build: rename repository folder ChatTwo to HellionChat
Repository folder, csproj, solution and all CI/build paths now use
the consolidated HellionChat name.
- ChatTwo/ → HellionChat/ (git mv preserves history with --follow)
- ChatTwo.csproj → HellionChat.csproj
- ChatTwo.sln → HellionChat.sln; obsolete Tests project entry removed
(private/untracked sandbox)
- AssemblyInfo.cs InternalsVisibleTo for ChatTwo.Tests removed
(file emptied; can be repopulated when actual tests land)
- repo.json and yaml image URLs updated (ChatTwo/images/ → HellionChat/images/)
- .github/workflows/{build,codeql,release}.yml csproj paths
- .github/dependabot.yml directory path
Functional behavior unchanged.
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using HellionChat.Code;
|
||||
using HellionChat.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace HellionChat.Ui;
|
||||
|
||||
// Hellion Chat — v0.6.0 input bar component for pop-out windows.
|
||||
//
|
||||
// Pragmatischer Refactor-Scope: Render() ist ein leerer Marker-Stub für
|
||||
// das Hauptfenster — der bestehende Input-Layer in ChatLogWindow bleibt
|
||||
// unangetastet, weil ein 400-Zeilen-Extract aus einem 1926-Zeilen-File
|
||||
// das v0.6.0-Risiko unverhältnismäßig erhöhen würde. Pop-Outs nutzen
|
||||
// ausschließlich RenderCompact(), das ist der ganze v0.6.0-Mehrwert.
|
||||
// Sollte das Hauptfenster selber später eine Compact-Variante brauchen
|
||||
// (oder das große Extract sich aus anderem Grund lohnen), kann Render()
|
||||
// in einem späteren Cycle gefüllt werden.
|
||||
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. v0.6.0 belässt den Hauptfenster-Input wie er ist.
|
||||
public void Render()
|
||||
{
|
||||
}
|
||||
|
||||
// Compact rendering for pop-out windows.
|
||||
//
|
||||
// v0.6.0 Compact-Layout: Channel-Icon-Button links (Background-Farbe
|
||||
// aus ChatColours), Text-Input rechts daneben. Auto-Translate-Picker
|
||||
// ist bewusst NICHT im Compact-Mode (Spec-Abweichung Layout D → A).
|
||||
// Rechtfertigung: das Hauptfenster-Auto-Complete-Popup ist nicht ohne
|
||||
// grossen Refactor pro Window instanzierbar; typische Pop-Out-Use-Cases
|
||||
// (FC-Greeter, Club-Hostess) brauchen Auto-Translate selten dort.
|
||||
// Eigene Compact-Auto-Complete-Implementation kann ein späterer
|
||||
// Cycle nachreichen wenn Tester-Feedback das verlangt.
|
||||
//
|
||||
// Channel-Switch wirkt via Plugin.Functions.Chat global (FFXIV-API).
|
||||
// Pro Pop-Out unabhängig bleiben Text-Buffer und History-Cursor.
|
||||
public void RenderCompact()
|
||||
{
|
||||
var tab = _activeTabAccessor();
|
||||
if (tab == null)
|
||||
return;
|
||||
|
||||
DrawChannelIconButton(tab);
|
||||
ImGui.SameLine();
|
||||
DrawCompactInput(tab);
|
||||
}
|
||||
|
||||
private void DrawCompactInput(Tab tab)
|
||||
{
|
||||
// Input takes the whole remaining width — no auto-translate button
|
||||
// reserved on the right side in v0.6.0 (see RenderCompact comment).
|
||||
var inputWidth = ImGui.GetContentRegionAvail().X;
|
||||
if (inputWidth < 60f)
|
||||
inputWidth = 60f;
|
||||
|
||||
ImGui.SetNextItemWidth(inputWidth);
|
||||
|
||||
// CallbackHistory wires up Up/Down navigation against the shared
|
||||
// InputHistoryService. Submit is detected the same way the main
|
||||
// window does it: via IsItemDeactivated + Enter, NOT EnterReturnsTrue
|
||||
// (matching v0.5.x ChatLogWindow.cs 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void SubmitCompact(Tab tab)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_state.Buffer))
|
||||
return;
|
||||
|
||||
var text = _state.Buffer;
|
||||
_state.Buffer = string.Empty;
|
||||
_state.HistoryCursor = -1;
|
||||
_host.SendChatBoxFromExternal(tab, text);
|
||||
}
|
||||
|
||||
// History-navigation callback for the compact input. Mirrors the main
|
||||
// window's logic but operates on _state.HistoryCursor and the shared
|
||||
// InputHistoryService. Index semantics match v0.5.x InputBacklog:
|
||||
// 0 = oldest, Count-1 = newest.
|
||||
private unsafe int CompactCallback(scoped ref ImGuiInputTextCallbackData data)
|
||||
{
|
||||
if (data.EventFlag != ImGuiInputTextFlags.CallbackHistory)
|
||||
return 0;
|
||||
|
||||
var prev = _state.HistoryCursor;
|
||||
switch (data.EventKey)
|
||||
{
|
||||
case ImGuiKey.UpArrow:
|
||||
switch (_state.HistoryCursor)
|
||||
{
|
||||
case -1:
|
||||
var offset = 0;
|
||||
if (!string.IsNullOrWhiteSpace(_state.Buffer))
|
||||
{
|
||||
InputHistoryService.Push(_state.Buffer);
|
||||
offset = 1;
|
||||
}
|
||||
_state.HistoryCursor = InputHistoryService.Count - 1 - offset;
|
||||
break;
|
||||
case > 0:
|
||||
_state.HistoryCursor--;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ImGuiKey.DownArrow:
|
||||
if (_state.HistoryCursor != -1)
|
||||
if (++_state.HistoryCursor >= InputHistoryService.Count)
|
||||
_state.HistoryCursor = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (prev == _state.HistoryCursor)
|
||||
return 0;
|
||||
|
||||
var historyStr = InputHistoryService.GetByCursor(_state.HistoryCursor) ?? string.Empty;
|
||||
data.DeleteChars(0, data.BufTextLen);
|
||||
data.InsertChars(0, historyStr);
|
||||
|
||||
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);
|
||||
|
||||
// Compute readable foreground — black on bright, 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 derived from the channel — quick visual cue
|
||||
// until we have a proper icon font available in the compact bar.
|
||||
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 so all windows
|
||||
// navigate the same active-tab pointer (single source of truth).
|
||||
public void HandleKeybindForward(int delta)
|
||||
{
|
||||
_host.ChangeTabDelta(delta);
|
||||
}
|
||||
}
|
||||
|
||||
// Per-window input state. Each ChatInputBar instance owns one of these
|
||||
// so pop-outs and the main window keep independent buffers and channels
|
||||
// (State-Sync-Entscheidung A in the v0.6.0 spec).
|
||||
public sealed class InputState
|
||||
{
|
||||
public string Buffer = string.Empty;
|
||||
public InputChannel? Channel;
|
||||
public int HistoryCursor = -1;
|
||||
}
|
||||
Reference in New Issue
Block a user