Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions ChatTwo/Ipc/QuickSymbolsIpc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using ChatTwo.Ui.Handler;
using Dalamud.Plugin.Ipc;

namespace ChatTwo;

public sealed class QuickSymbolsIpc : IDisposable
{
private const string Owner = "ChatTwo";
private const string WatchInput = "QuickSymbols.WatchInput";
private const string ClosePicker = "QuickSymbols.ClosePicker";
private const string SymbolSelected = "QuickSymbols.SymbolSelected";

private ICallGateSubscriber<string, bool>? watch;
private ICallGateSubscriber<string, bool>? close;
private ICallGateSubscriber<string, string, object?>? selected;
private Action<string, string>? onSelected;

// This is for tracking the input that most recently advertised itself as active.
// QuickSymbols will send the selected symbol back ONLY through IPC; ChatTwo will still own the buffer and caret.
private InputHandler? input;

public QuickSymbolsIpc(Plugin _)
{
Register();
}

public void Watch(InputHandler handler, bool state)
{
// A false state means that this handler should no longer receive symbols.
// This will preserve any other handler that became operational post this one.
if (!state)
{
if (ReferenceEquals(input, handler))
input = null;

return;
}

input = handler;

try
{
// The active input tells QuickSymbols that it is safe to react
// to the user QuickSymbols keybind but only for this owner and while the input is active.
watch?.InvokeFunc(Owner);
}
catch
{
// QuickSymbols is optional. Won't do anything unless called.
}
}

private void Register()
{
try
{
// These subscribers will only be utilized if QuickSymbols is loaded
// and the IPC available. Otherwise, ChatTwo will carry on functioning as it normally does.
watch = Plugin.Interface.GetIpcSubscriber<string, bool>(WatchInput);
close = Plugin.Interface.GetIpcSubscriber<string, bool>(ClosePicker);
selected = Plugin.Interface.GetIpcSubscriber<string, string, object?>(SymbolSelected);

// QuickSymbols doesnt modify ChatTwo input itself. It simply displays
// the chosen symbol in this location and then ChatTwo should handle its insertion via its own InputHandler.
onSelected = OnSelected;
selected.Subscribe(onSelected);
}
catch (Exception ex)
{
Plugin.Log.Debug(ex, "QuickSymbols IPC could not be registered.");
}
}

private void Close()
{
try
{
// This will terminate any picker session that was initiated for ChatTwo.
// It should prevent outdated QuickSymbols target from remaining once ChatTwo has been unloaded.
close?.InvokeFunc(Owner);
}
catch
{

}
}

private void OnSelected(string owner, string symbol)
{
// Multiple plugins may pick up on the same occurrence,
// therefore the owner key is to ensure that each setup stays separate.
if (owner != Owner || string.IsNullOrEmpty(symbol))
return;

// This approach is to ensure that the text buffer, blinking cursor and the
// restoration of focus are all managed by ChatTwo itself.
// For this to work, it channels the input directly through ChatTwo internal mechanisms.
input?.InsertText(symbol);
}

public void Dispose()
{
// Any active picker is closed before the IPC subscriber goes away.
Close();

if (selected != null && onSelected != null)
{
selected.Unsubscribe(onSelected);
onSelected = null;
}
}
}
4 changes: 3 additions & 1 deletion ChatTwo/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Globalization;
using System.Numerics;
using ChatTwo.Http;
using ChatTwo.Ipc;
using ChatTwo.Resources;
using ChatTwo.Ui;
using ChatTwo.Ui.ChatLog;
Expand Down Expand Up @@ -62,6 +61,7 @@ public sealed class Plugin : IDalamudPlugin
public IpcManager Ipc { get; }
public ExtraChat ExtraChat { get; }
public TypingIpc TypingIpc { get; }
public QuickSymbolsIpc QuickSymbols { get; }
public FontManager FontManager { get; }

public readonly ServerCore ServerCore;
Expand Down Expand Up @@ -132,6 +132,7 @@ public Plugin()
Functions = new GameFunctions.GameFunctions(this);
Ipc = new IpcManager();
TypingIpc = new TypingIpc(this);
QuickSymbols = new QuickSymbolsIpc(this);
ExtraChat = new ExtraChat();
FontManager = new FontManager();

Expand Down Expand Up @@ -216,6 +217,7 @@ public void Dispose()
DebuggerWindow?.Dispose();
SeStringDebugger?.Dispose();

QuickSymbols?.Dispose();
TypingIpc?.Dispose();
ExtraChat?.Dispose();
Ipc?.Dispose();
Expand Down
61 changes: 58 additions & 3 deletions ChatTwo/Ui/Handler/InputHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Numerics;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using ChatTwo.Code;
using ChatTwo.GameFunctions;
using ChatTwo.GameFunctions.Types;
Expand Down Expand Up @@ -31,6 +32,11 @@ public class InputHandler

public string ChatInput = string.Empty;

private string? pendingText;
private int pendingPos = -1;
private int qsFocusFrames;
private int qsCaretFrames;

public bool FocusedPreview;
public bool Activate;
public bool InputFocused;
Expand All @@ -57,6 +63,19 @@ public InputHandler(IChatWindow mainWindow, Plugin plugin, string id)
AutoCompleteHandler = new AutoCompleteHandler(this);
}

public void InsertText(string text)
{
if (string.IsNullOrEmpty(text))
return;

pendingText += text;
pendingPos = CursorPos;
Activate = true;
ActivatePos = CursorPos;
qsFocusFrames = 6;
qsCaretFrames = 90;
}

public void DrawInputArea(Tab activeTab, float inputWidth, ref bool tellSpecial)
{
var inputType = activeTab.CurrentChannel.UseTempChannel
Expand Down Expand Up @@ -87,9 +106,12 @@ public void DrawInputArea(Tab activeTab, float inputWidth, ref bool tellSpecial)
using (ImRaii.PushColor(ImGuiCol.Text, push ? ColourUtil.RgbaToAbgr(inputColour!.Value) : 0, push))
{
var isChatEnabled = activeTab is { InputDisabled: false };
if (isChatEnabled && (Activate || FocusedPreview))
if (isChatEnabled && (Activate || FocusedPreview || qsFocusFrames > 0))
{
FocusedPreview = false;
if (qsFocusFrames > 0)
qsFocusFrames--;

ImGui.SetKeyboardFocusHere();
}

Expand All @@ -100,9 +122,29 @@ public void DrawInputArea(Tab activeTab, float inputWidth, ref bool tellSpecial)
ImGui.SetNextItemWidth(inputWidth);
ImGui.InputTextWithHint("##chat2-input", isChatEnabled ? "": Language.ChatLog_DisabledInput, ref ChatInput, 500, flags, Callback);
}
var inputActive = ImGui.IsItemActive();
var inputMin = ImGui.GetItemRectMin();
var inputMax = ImGui.GetItemRectMax();
var inputActive = ImGui.IsItemActive() || ImGui.IsItemFocused();
InputFocused = isChatEnabled && inputActive;

if (qsCaretFrames > 0)
qsCaretFrames--;

Plugin.QuickSymbols.Watch(this, isChatEnabled && (inputActive || qsFocusFrames > 0 || qsCaretFrames > 0));

if (!inputActive && qsCaretFrames > 0)
{
var caretAlpha = (ImGui.GetTime() % 1.0) < 0.5 ? 0.95f : 0.35f;
var textWidth = ImGui.CalcTextSize(ChatInput).X;
var caretX = MathF.Min(inputMax.X - 5f, inputMin.X + 8f + textWidth);
var caretY = inputMin.Y + 4f;
ImGui.GetWindowDrawList().AddLine(
new Vector2(caretX, caretY),
new Vector2(caretX, inputMax.Y - 4f),
ImGui.GetColorU32(new Vector4(1f, 1f, 1f, caretAlpha)),
1f);
}

var tooltipDraw = Plugin.Config.PreviewPosition is PreviewPosition.Tooltip && Plugin.InputPreview.IsDrawable;
if (tooltipDraw && ImGui.IsItemHovered())
{
Expand Down Expand Up @@ -232,6 +274,19 @@ private unsafe int Callback(scoped ref ImGuiInputTextCallbackData data)
ActivatePos = -1;
}

if (pendingText != null)
{
var insert = pendingText;
var pos = pendingPos > -1 ? Math.Min(pendingPos, data.BufTextLen) : data.CursorPos;

pendingText = null;
pendingPos = -1;

data.InsertChars(pos, insert);
data.CursorPos = pos + Encoding.UTF8.GetByteCount(insert);
data.SelectionStart = data.SelectionEnd = data.CursorPos;
}

Plugin.CommandHelpWindow.IsOpen = false;
var text = MemoryHelper.ReadString((nint) data.Buf, data.BufTextLen);
if (text.StartsWith('/'))
Expand Down