Merge pull request #2350 from Soreepeong/fix/imgui

Temporarily fix IME
This commit is contained in:
goat 2025-08-07 13:22:07 +02:00 committed by GitHub
commit abb017f190
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 142 additions and 219 deletions

View file

@ -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
{

View 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;
}
}