mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
commit
abb017f190
2 changed files with 142 additions and 219 deletions
|
|
@ -46,24 +46,24 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
.ToDictionary(x => x.Item1, x => x.Name);
|
||||
|
||||
private static readonly UnicodeRange[] HanRange =
|
||||
{
|
||||
[
|
||||
UnicodeRanges.CjkRadicalsSupplement,
|
||||
UnicodeRanges.CjkSymbolsandPunctuation,
|
||||
UnicodeRanges.CjkUnifiedIdeographsExtensionA,
|
||||
UnicodeRanges.CjkUnifiedIdeographs,
|
||||
UnicodeRanges.CjkCompatibilityIdeographs,
|
||||
UnicodeRanges.CjkCompatibilityForms,
|
||||
UnicodeRanges.CjkCompatibilityForms
|
||||
// No more; Extension B~ are outside BMP range
|
||||
};
|
||||
];
|
||||
|
||||
private static readonly UnicodeRange[] HangulRange =
|
||||
{
|
||||
[
|
||||
UnicodeRanges.HangulJamo,
|
||||
UnicodeRanges.HangulSyllables,
|
||||
UnicodeRanges.HangulCompatibilityJamo,
|
||||
UnicodeRanges.HangulJamoExtendedA,
|
||||
UnicodeRanges.HangulJamoExtendedB,
|
||||
};
|
||||
UnicodeRanges.HangulJamoExtendedB
|
||||
];
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -109,24 +109,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
private bool updateInputLanguage = true;
|
||||
private bool updateImeStatusAgain;
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")]
|
||||
static DalamudIme()
|
||||
{
|
||||
nint cimgui;
|
||||
try
|
||||
{
|
||||
_ = ImGui.GetCurrentContext();
|
||||
|
||||
cimgui = Process.GetCurrentProcess().Modules.Cast<ProcessModule>()
|
||||
.First(x => x.ModuleName == "cimgui.dll")
|
||||
.BaseAddress;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudIme(InterfaceManager.InterfaceManagerWithScene imws)
|
||||
{
|
||||
|
|
@ -170,11 +152,11 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
if (!ImGui.GetIO().ConfigInputTextCursorBlink)
|
||||
return true;
|
||||
var textState = GetInputTextState();
|
||||
if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0)
|
||||
if (textState.ID == 0 || (textState.Flags & ImGuiInputTextFlags.ReadOnly) != 0)
|
||||
return true;
|
||||
if (textState->CursorAnim <= 0)
|
||||
if (textState.CursorAnim <= 0)
|
||||
return true;
|
||||
return textState->CursorAnim % 1.2f <= 0.8f;
|
||||
return textState.CursorAnim % 1.2f <= 0.8f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,11 +209,8 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
private static ImGuiInputTextStateWrapper* GetInputTextState()
|
||||
{
|
||||
var ctx = ImGui.GetCurrentContext();
|
||||
return (ImGuiInputTextStateWrapper*)&ctx.Handle->InputTextState;
|
||||
}
|
||||
private static ImGuiInputTextStatePtr GetInputTextState() =>
|
||||
(ImGuiInputTextState*)((nint)ImGui.GetCurrentContext().Handle + 0x4588);
|
||||
|
||||
private static (string String, bool Supported) ToUcs2(char* data, int nc = -1)
|
||||
{
|
||||
|
|
@ -332,7 +311,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
try
|
||||
{
|
||||
var textState = GetInputTextState();
|
||||
var invalidTarget = textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0;
|
||||
var invalidTarget = textState.ID == 0 || (textState.Flags & ImGuiInputTextFlags.ReadOnly) != 0;
|
||||
|
||||
#if IMEDEBUG
|
||||
switch (args.Message)
|
||||
|
|
@ -564,17 +543,17 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
var textState = GetInputTextState();
|
||||
if (this.temporaryUndoSelection is not null)
|
||||
{
|
||||
textState->Undo();
|
||||
textState->SelectionTuple = this.temporaryUndoSelection.Value;
|
||||
textState.Undo();
|
||||
textState.SetSelectionTuple(this.temporaryUndoSelection.Value);
|
||||
this.temporaryUndoSelection = null;
|
||||
}
|
||||
|
||||
textState->SanitizeSelectionRange();
|
||||
if (textState->ReplaceSelectionAndPushUndo(newString))
|
||||
this.temporaryUndoSelection = textState->SelectionTuple;
|
||||
textState.SanitizeSelectionRange();
|
||||
if (textState.ReplaceSelectionAndPushUndo(newString))
|
||||
this.temporaryUndoSelection = textState.GetSelectionTuple();
|
||||
|
||||
// Put the cursor at the beginning, so that the candidate window appears aligned with the text.
|
||||
textState->SetSelectionRange(textState->SelectionTuple.Start, newString.Length, 0);
|
||||
textState.SetSelectionRange(textState.GetSelectionTuple().Start, newString.Length, 0);
|
||||
|
||||
if (finalCommit)
|
||||
{
|
||||
|
|
@ -621,7 +600,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
this.temporaryUndoSelection = null;
|
||||
|
||||
var textState = GetInputTextState();
|
||||
textState->Stb.SelectStart = textState->Stb.Cursor = textState->Stb.SelectEnd;
|
||||
textState.Stb.SelectStart = textState.Stb.Cursor = textState.Stb.SelectEnd;
|
||||
|
||||
this.candidateStrings.Clear();
|
||||
this.immCandNative = default;
|
||||
|
|
@ -931,185 +910,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ported from imstb_textedit.h.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0xE2C)]
|
||||
private struct StbTextEditState
|
||||
{
|
||||
/// <summary>
|
||||
/// Position of the text cursor within the string.
|
||||
/// </summary>
|
||||
public int Cursor;
|
||||
|
||||
/// <summary>
|
||||
/// Selection start point.
|
||||
/// </summary>
|
||||
public int SelectStart;
|
||||
|
||||
/// <summary>
|
||||
/// selection start and end point in characters; if equal, no selection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that start may be less than or greater than end (e.g. when dragging the mouse,
|
||||
/// start is where the initial click was, and you can drag in either direction.)
|
||||
/// </remarks>
|
||||
public int SelectEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Each text field keeps its own insert mode state.
|
||||
/// To keep an app-wide insert mode, copy this value in/out of the app state.
|
||||
/// </summary>
|
||||
public byte InsertMode;
|
||||
|
||||
/// <summary>
|
||||
/// Page size in number of row.
|
||||
/// This value MUST be set to >0 for pageup or pagedown in multilines documents.
|
||||
/// </summary>
|
||||
public int RowCountPerPage;
|
||||
|
||||
// Remainder is stb-private data.
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct ImGuiInputTextStateWrapper
|
||||
{
|
||||
public uint Id;
|
||||
public int CurLenW;
|
||||
public int CurLenA;
|
||||
public ImVector<char> TextWRaw;
|
||||
public ImVector<byte> TextARaw;
|
||||
public ImVector<byte> InitialTextARaw;
|
||||
public bool TextAIsValid;
|
||||
public int BufCapacityA;
|
||||
public float ScrollX;
|
||||
public StbTextEditState Stb;
|
||||
public float CursorAnim;
|
||||
public bool CursorFollow;
|
||||
public bool SelectedAllMouseLock;
|
||||
public bool Edited;
|
||||
public ImGuiInputTextFlags Flags;
|
||||
|
||||
public ImVectorWrapper<char> TextW => new((ImVector*)&this.ThisWrapperPtr->TextWRaw);
|
||||
|
||||
public (int Start, int End, int Cursor) SelectionTuple
|
||||
{
|
||||
get => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor);
|
||||
set => (this.Stb.SelectStart, this.Stb.SelectEnd, this.Stb.Cursor) = value;
|
||||
}
|
||||
|
||||
private ImGuiInputTextStateWrapper* ThisWrapperPtr => (ImGuiInputTextStateWrapper*)Unsafe.AsPointer(ref this);
|
||||
|
||||
private ImGuiInputTextState* ThisPtr => (ImGuiInputTextState*)Unsafe.AsPointer(ref this);
|
||||
|
||||
public void SetSelectionRange(int offset, int length, int relativeCursorOffset)
|
||||
{
|
||||
this.Stb.SelectStart = offset;
|
||||
this.Stb.SelectEnd = offset + length;
|
||||
if (relativeCursorOffset >= 0)
|
||||
this.Stb.Cursor = this.Stb.SelectStart + relativeCursorOffset;
|
||||
else
|
||||
this.Stb.Cursor = this.Stb.SelectEnd + 1 + relativeCursorOffset;
|
||||
this.SanitizeSelectionRange();
|
||||
}
|
||||
|
||||
public void SanitizeSelectionRange()
|
||||
{
|
||||
ref var s = ref this.Stb.SelectStart;
|
||||
ref var e = ref this.Stb.SelectEnd;
|
||||
ref var c = ref this.Stb.Cursor;
|
||||
s = Math.Clamp(s, 0, this.CurLenW);
|
||||
e = Math.Clamp(e, 0, this.CurLenW);
|
||||
c = Math.Clamp(c, 0, this.CurLenW);
|
||||
if (s == e)
|
||||
s = e = c;
|
||||
if (s > e)
|
||||
(s, e) = (e, s);
|
||||
}
|
||||
|
||||
public void Undo() => ImGuiP.Custom_StbTextUndo(this.ThisPtr);
|
||||
|
||||
public bool MakeUndoReplace(int offset, int oldLength, int newLength)
|
||||
{
|
||||
if (oldLength == 0 && newLength == 0)
|
||||
return false;
|
||||
|
||||
ImGuiP.Custom_StbTextMakeUndoReplace(this.ThisPtr, offset, oldLength, newLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReplaceSelectionAndPushUndo(ReadOnlySpan<char> newText)
|
||||
{
|
||||
var off = this.Stb.SelectStart;
|
||||
var len = this.Stb.SelectEnd - this.Stb.SelectStart;
|
||||
return this.MakeUndoReplace(off, len, newText.Length) && this.ReplaceChars(off, len, newText);
|
||||
}
|
||||
|
||||
public bool ReplaceChars(int pos, int len, ReadOnlySpan<char> newText)
|
||||
{
|
||||
this.DeleteChars(pos, len);
|
||||
return this.InsertChars(pos, newText);
|
||||
}
|
||||
|
||||
// See imgui_widgets.cpp: STB_TEXTEDIT_DELETECHARS
|
||||
public void DeleteChars(int pos, int n)
|
||||
{
|
||||
if (n == 0)
|
||||
return;
|
||||
|
||||
var dst = this.TextW.Data + pos;
|
||||
|
||||
// We maintain our buffer length in both UTF-8 and wchar formats
|
||||
this.Edited = true;
|
||||
this.CurLenA -= Encoding.UTF8.GetByteCount(dst, n);
|
||||
this.CurLenW -= n;
|
||||
|
||||
// Offset remaining text (FIXME-OPT: Use memmove)
|
||||
var src = this.TextW.Data + pos + n;
|
||||
int i;
|
||||
for (i = 0; src[i] != 0; i++)
|
||||
dst[i] = src[i];
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
// See imgui_widgets.cpp: STB_TEXTEDIT_INSERTCHARS
|
||||
public bool InsertChars(int pos, ReadOnlySpan<char> newText)
|
||||
{
|
||||
if (newText.Length == 0)
|
||||
return true;
|
||||
|
||||
var isResizable = (this.Flags & ImGuiInputTextFlags.CallbackResize) != 0;
|
||||
var textLen = this.CurLenW;
|
||||
Debug.Assert(pos <= textLen, "pos <= text_len");
|
||||
|
||||
var newTextLenUtf8 = Encoding.UTF8.GetByteCount(newText);
|
||||
if (!isResizable && newTextLenUtf8 + this.CurLenA + 1 > this.BufCapacityA)
|
||||
return false;
|
||||
|
||||
// Grow internal buffer if needed
|
||||
if (newText.Length + textLen + 1 > this.TextW.Length)
|
||||
{
|
||||
if (!isResizable)
|
||||
return false;
|
||||
|
||||
Debug.Assert(textLen < this.TextW.Length, "text_len < this.TextW.Length");
|
||||
this.TextW.Resize(textLen + Math.Clamp(newText.Length * 4, 32, Math.Max(256, newText.Length)) + 1);
|
||||
}
|
||||
|
||||
var text = this.TextW.DataSpan;
|
||||
if (pos != textLen)
|
||||
text.Slice(pos, textLen - pos).CopyTo(text[(pos + newText.Length)..]);
|
||||
newText.CopyTo(text[pos..]);
|
||||
|
||||
this.Edited = true;
|
||||
this.CurLenW += newText.Length;
|
||||
this.CurLenA += newTextLenUtf8;
|
||||
this.TextW[this.CurLenW] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#if IMEDEBUG
|
||||
private static class ImeDebug
|
||||
{
|
||||
|
|
|
|||
123
Dalamud/Interface/Internal/ImGuiInputTextStatePtrExtensions.cs
Normal file
123
Dalamud/Interface/Internal/ImGuiInputTextStatePtrExtensions.cs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
#pragma warning disable SA1600
|
||||
internal static unsafe class ImGuiInputTextStatePtrExtensions
|
||||
{
|
||||
public static (int Start, int End, int Cursor) GetSelectionTuple(this ImGuiInputTextStatePtr self) =>
|
||||
(self.Stb.SelectStart, self.Stb.SelectEnd, self.Stb.Cursor);
|
||||
|
||||
public static void SetSelectionTuple(this ImGuiInputTextStatePtr self, (int Start, int End, int Cursor) value) =>
|
||||
(self.Stb.SelectStart, self.Stb.SelectEnd, self.Stb.Cursor) = value;
|
||||
|
||||
public static void SetSelectionRange(this ImGuiInputTextStatePtr self, int offset, int length, int relativeCursorOffset)
|
||||
{
|
||||
self.Stb.SelectStart = offset;
|
||||
self.Stb.SelectEnd = offset + length;
|
||||
if (relativeCursorOffset >= 0)
|
||||
self.Stb.Cursor = self.Stb.SelectStart + relativeCursorOffset;
|
||||
else
|
||||
self.Stb.Cursor = self.Stb.SelectEnd + 1 + relativeCursorOffset;
|
||||
self.SanitizeSelectionRange();
|
||||
}
|
||||
|
||||
public static void SanitizeSelectionRange(this ImGuiInputTextStatePtr self)
|
||||
{
|
||||
ref var s = ref self.Stb.SelectStart;
|
||||
ref var e = ref self.Stb.SelectEnd;
|
||||
ref var c = ref self.Stb.Cursor;
|
||||
s = Math.Clamp(s, 0, self.CurLenW);
|
||||
e = Math.Clamp(e, 0, self.CurLenW);
|
||||
c = Math.Clamp(c, 0, self.CurLenW);
|
||||
if (s == e)
|
||||
s = e = c;
|
||||
if (s > e)
|
||||
(s, e) = (e, s);
|
||||
}
|
||||
|
||||
public static void Undo(this ImGuiInputTextStatePtr self) => ImGuiP.Custom_StbTextUndo(self);
|
||||
|
||||
public static bool MakeUndoReplace(this ImGuiInputTextStatePtr self, int offset, int oldLength, int newLength)
|
||||
{
|
||||
if (oldLength == 0 && newLength == 0)
|
||||
return false;
|
||||
|
||||
ImGuiP.Custom_StbTextMakeUndoReplace(self, offset, oldLength, newLength);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ReplaceSelectionAndPushUndo(this ImGuiInputTextStatePtr self, ReadOnlySpan<char> newText)
|
||||
{
|
||||
var off = self.Stb.SelectStart;
|
||||
var len = self.Stb.SelectEnd - self.Stb.SelectStart;
|
||||
return self.MakeUndoReplace(off, len, newText.Length) && self.ReplaceChars(off, len, newText);
|
||||
}
|
||||
|
||||
public static bool ReplaceChars(this ImGuiInputTextStatePtr self, int pos, int len, ReadOnlySpan<char> newText)
|
||||
{
|
||||
self.DeleteChars(pos, len);
|
||||
return self.InsertChars(pos, newText);
|
||||
}
|
||||
|
||||
// See imgui_widgets.cpp: STB_TEXTEDIT_DELETECHARS
|
||||
public static void DeleteChars(this ImGuiInputTextStatePtr self, int pos, int n)
|
||||
{
|
||||
if (n == 0)
|
||||
return;
|
||||
|
||||
var dst = (char*)self.TextW.Data + pos;
|
||||
|
||||
// We maintain our buffer length in both UTF-8 and wchar formats
|
||||
self.Edited = true;
|
||||
self.CurLenA -= Encoding.UTF8.GetByteCount(dst, n);
|
||||
self.CurLenW -= n;
|
||||
|
||||
// Offset remaining text (FIXME-OPT: Use memmove)
|
||||
var src = (char*)self.TextW.Data + pos + n;
|
||||
int i;
|
||||
for (i = 0; src[i] != 0; i++)
|
||||
dst[i] = src[i];
|
||||
dst[i] = '\0';
|
||||
}
|
||||
|
||||
// See imgui_widgets.cpp: STB_TEXTEDIT_INSERTCHARS
|
||||
public static bool InsertChars(this ImGuiInputTextStatePtr self, int pos, ReadOnlySpan<char> newText)
|
||||
{
|
||||
if (newText.Length == 0)
|
||||
return true;
|
||||
|
||||
var isResizable = (self.Flags & ImGuiInputTextFlags.CallbackResize) != 0;
|
||||
var textLen = self.CurLenW;
|
||||
Debug.Assert(pos <= textLen, "pos <= text_len");
|
||||
|
||||
var newTextLenUtf8 = Encoding.UTF8.GetByteCount(newText);
|
||||
if (!isResizable && newTextLenUtf8 + self.CurLenA + 1 > self.BufCapacityA)
|
||||
return false;
|
||||
|
||||
// Grow internal buffer if needed
|
||||
if (newText.Length + textLen + 1 > self.TextW.Size)
|
||||
{
|
||||
if (!isResizable)
|
||||
return false;
|
||||
|
||||
Debug.Assert(textLen < self.TextW.Size, "text_len < self.TextW.Length");
|
||||
self.TextW.Resize(textLen + Math.Clamp(newText.Length * 4, 32, Math.Max(256, newText.Length)) + 1);
|
||||
}
|
||||
|
||||
var text = new Span<char>(self.TextW.Data, self.TextW.Size);
|
||||
if (pos != textLen)
|
||||
text.Slice(pos, textLen - pos).CopyTo(text[(pos + newText.Length)..]);
|
||||
newText.CopyTo(text[pos..]);
|
||||
|
||||
self.Edited = true;
|
||||
self.CurLenW += newText.Length;
|
||||
self.CurLenA += newTextLenUtf8;
|
||||
self.TextW[self.CurLenW] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue